Linux 开发学习 (三) taro Posted on Mar 18 2021 Linux 笔记 开发 # 系统调用 ## lseek函数: ```c off_t lseek(int fd, off_t offset, int whence); ``` 参数: - fd:文件描述符 - offset: 偏移量,就是将读写指针从whence指定位置向后偏移offset个单位 - whence:起始偏移位置: SEEK_SET/SEEK_CUR/SEEK_END 返回值: - 成功:较起始位置偏移量 - 失败:-1 errno 应用场景: - 文件的“读”、“写”使用同一偏移位置。 - 使用lseek获取文件大小 - 使用lseek拓展文件大小:要想使文件大小真正拓展,必须引起IO操作。 - 使用 truncate 函数,直接拓展文件。 int ret = truncate("dict.cp", 250); lseek示例,写一个句子到空白文件,完事调整光标位置,读取刚才写那个文件。 这个示例中,如果不调整光标位置,是读取不到内容的,因为读写指针在内容的末尾 代码如下: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h> int main(void) { int fd, n; char msg[] = "It's a test for lseek\n"; char ch; fd = open("lseek.txt", O_RDWR|O_CREAT, 0644); if(fd < 0){ perror("open lseek.txt error"); exit(1); } write(fd, msg, strlen(msg)); //使用fd对打开的文件进行写操作,问价读写位置位于文件结尾处。 lseek(fd, 0, SEEK_SET); //修改文件读写指针位置,位于文件开头。 注释该行会怎样呢? while((n = read(fd, &ch, 1))){ if(n < 0){ perror("read error"); exit(1); } write(STDOUT_FILENO, &ch, n); //将文件内容按字节读出,写出到屏幕 } close(fd); return 0; } ``` 下面这个代码用lseek的偏移来读取文件大小: ![](/api/file/getImage?fileId=6052210fef4611000d00000c) 下面使用lseek将fcntl.c原本的698字节填充为800字节,差值102字节: 比起读取大小的代码,就改了个偏移量 ![](/api/file/getImage?fileId=6052210fef4611000d00000b) 引起IO操作,使文件大小真正拓展 ![](/api/file/getImage?fileId=6052210fef4611000d00000d) > 对于写文件再读取那个例子,由于文件写完之后未关闭,读写指针在文件末尾,所以不调节指针,直接读取不到内容。 lseek读取的文件大小总是相对文件头部而言。 用lseek读取文件大小实际用的是读写指针初末位置的偏移差,一个新开文件,读写指针初位置都在文件开头。如果用这个来扩展文件大小,必须引起IO才行,于是就至少要写入一个字符。上面代码出现lseek返回799,ls查看为800的原因是,lseek读取到偏移差的时候,还没有写入最后的‘$’符号. 末尾那一大堆^@,是文件空洞,如果自己写进去的也想保持队形,就写入“\0”。 拓展文件直接使用truncate,简单粗暴: 使用 truncate 函数,直接拓展文件。 int ret = truncate("dict.cp", 250); ## stat/lstat函数 获取文件属性,(从inode结构体中获取) stat/lstat 函数: ```c int stat(const char *path, struct stat *buf); int lstat(const char *path, struct stat *buf); ``` 参数: - path: 文件路径 - buf:(传出参数) 存放文件属性,inode结构体指针。 返回值: - 成功: 0 - 失败: -1 errno - 获取文件大小: buf.st_size - 获取文件类型: buf.st_mode - 获取文件权限: buf.st_mode 符号穿透:stat会。lstat不会。 下面这个例子是获取文件大小的正规军解法,用stat: ![](/api/file/getImage?fileId=605221d6ef4611000d000010) stat会拿到符号链接指向那个文件或目录的属性。 不想穿透符号就用lstat lstat ![](/api/file/getImage?fileId=605221d6ef4611000d00000f) 文件属性位图: ![](/api/file/getImage?fileId=605222e9ef4611000d000011) ## 目录操作函数 ```c DIR * opendir(char *name); int closedir(DIR *dp); struct dirent *readdir(DIR * dp); struct dirent { inode char dname[256]; } ``` 用目录操作函数实现一个ls操作 ![](/api/file/getImage?fileId=60522390ef4611000d000015) ## dup和dup2 用来做重定向,本质就是复制文件描述符 ```c int dup(int oldfd) ``` - oldfd: 已有文件描述符 - 返回:新文件描述符,这个描述符和oldfd指向相同内容。 ```c int dup2(int oldfd, int newfd); ``` 文件描述符复制,oldfd拷贝给newfd。返回newfd 给一个旧的文件描述符,返回一个新文件描述符 ![](/api/file/getImage?fileId=605223e2ef4611000d000016) 将一个已有文件描述符fd1复制给另一个文件描述符fd2,然后用fd2修改fd1指向的文件 ![](/api/file/getImage?fileId=605223faef4611000d000017) 上面那个例子,fd1是打开hello.c的文件描述符,fd2是打开hello2.c的文件描述符 用dup2将fd1复制给了fd2,于是在对fd2指向的文件进行写操作时,实际上就是对fd1指向的hello.c进行写操作。 这里需要注意一个问题,由于hello.c和hello2.c都是空文件,所以直接写进去没关系。但如果hello.c是非空的,写进去的内容默认从文件头部开始写,会覆盖原有内容。 将输出到STDOUT的内容重定向到文件里 ![](/api/file/getImage?fileId=60522428ef4611000d000019) # 传入传出参数 ## 传入参数 - 指针作为函数参数。 - 同常有const关键字修饰。 - 指针指向有效区域, 在函数内部做读操作。 ## 传出参数 - 指针作为函数参数。 - 在函数调用之前,指针指向的空间可以无意义,但必须有效。 - 在函数内部,做写操作。 - 函数调用结束后,充当函数返回值。 ## 传入传出参数 - 指针作为函数参数。 - 在函数调用之前,指针指向的空间有实际意义。 - 在函数内部,先做读操作,后做写操作。 - 函数调用结束后,充当函数返回值。 # 目录项和inode ![](/api/file/getImage?fileId=6052215fef4611000d00000e) 一个文件主要由两部分组成,dentry(目录项)和inode inode本质是结构体,存储文件的属性信息,如:权限、类型、大小、时间、用户、盘快位置… 也叫做文件属性管理结构,大多数的inode都存储在磁盘上。 少量常用、近期使用的inode会被缓存到内存中。 所谓的删除文件,就是删除inode,但是数据其实还是在硬盘上,以后会覆盖掉。 # link和Unlink隐式回收 ## link 硬链接数就是dentry数目 link就是用来创建硬链接的 link可以用来实现mv命令 函数原型: ```c int link(const char *oldpath, const char *newpath) int unlink(const char *pathname) ``` 用这个来实现mv,用oldpath来创建newpath,完事儿删除oldpath就行。 删除一个链接 int unlink(const char *pathname) unlink是删除一个文件的目录项dentry,使硬链接数-1 unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放,要等到所有打开文件的进程关闭该文件,系统才会挑时间将该文件释放掉。 下面用一段代码来验证unlink是删除dentry ```c /* *unlink函数是删除一个dentry */ #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <stdio.h> int main(void) { int fd, ret; char *p = "test of unlink\n"; char *p2 = "after write something.\n"; fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644); if(fd < 0){ perror("open temp error"); exit(1); } ret = write(fd, p, strlen(p)); if (ret == -1) { perror("-----write error"); } printf("hi! I'm printf\n"); ret = write(fd, p2, strlen(p2)); if (ret == -1) { perror("-----write error"); } printf("Enter anykey continue\n"); getchar(); ret = unlink("temp.txt"); //具备了被释放的条件 if(ret < 0){ perror("unlink error"); exit(1); } close(fd); return 0; } ``` 编译程序并运行,程序阻塞,此时打开新终端查看临时文件temp.c如下: ![](/api/file/getImage?fileId=605222e9ef4611000d000013) 可以看到,临时文件没有被删除,这是因为当前进程没结束。 输入字符使当前进程结束后,temp.txt就不见了 ![](/api/file/getImage?fileId=605222e9ef4611000d000012) 在程序中加入段错误成分,段错误在unlink之前,由于发生段错误,程序后续删除temp.txt的dentry部分就不会再执行,temp.txt就保留了下来,这是不科学的。 解决办法是检测fd有效性后,立即释放temp.txt,由于进程未结束,虽然temp.txt的硬链接数已经为0,但还不会立即释放,仍然存在,要等到程序执行完才会释放。这样就能避免程序出错导致临时文件保留下来。 因为文件创建后,硬链接数立马减为0,即使程序异常退出,这个文件也会被清理掉。这时候的内容是写在内核空间的缓冲区。 ```c /* *unlink函数是删除一个dentry */ #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <stdio.h> int main(void) { int fd, ret; char *p = "test of unlink\n"; char *p2 = "after write something.\n"; fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644); if(fd < 0){ perror("open temp error"); exit(1); } ret = unlink("temp.txt"); //具备了被释放的条件 if(ret < 0){ perror("unlink error"); exit(1); } ret = write(fd, p, strlen(p)); if (ret == -1) { perror("-----write error"); } printf("hi! I'm printf\n"); ret = write(fd, p2, strlen(p2)); if (ret == -1) { perror("-----write error"); } printf("Enter anykey continue\n"); getchar(); close(fd); return 0; } ``` ## 隐式回收 当进程结束运行时,所有进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。 比如上面那个程序,要是没有在程序中关闭文件描述符,没有隐式回收的话,这个文件描述符会保留,多次出现这种情况会导致系统文件描述符耗尽。所以隐式回收会在程序结束时收回它打开的文件使用的文件描述符。 # 文件目录rwx权限差异 vi 目录 会得到目录项的列表 ![](/api/file/getImage?fileId=60522339ef4611000d000014) RHEL6修改源 Linux 开发学习 (二)