文件描述符与文件的关系
在虚拟地址空间中,进程将打开的文件描述符放在task_struct的文件描述符表中,文件描述符指向一个文件表项,文件表项则指向文件的i-node节点。
i-node节点的具体结构可以参考Linux文件系统(一)中的描述,文件表项虽然是系统层面管理的,但对于相同的i-node,不同进程对应的文件表项是不同的,文件表项实际上是struct file结构体,文件描述符表就是元素为struct file的数组,同一进程中多次打开同一文件,对应的文件表项和i-node都相同,而不同进程打开同一文件,文件表项不同,i-node相同。特殊的是,父子进程共享fork前创建的文件表项。
从上图可以发现,文件表项中记录了文件偏移量,不同文件表项记录不同的文件偏移量,这就是不同进程间不会共享文件偏移量的原因。
close_on_exec
子进程fork时,会复制父进程的文件描述符和文件表项,这有可能带来意想不到的错误。比如父进程中打开了一个文件描述符,父进程fork出子进程后,子进程通过exec装载新的程序,父进程退出,此时子进程还拥有这个文件描述符,这是进程不希望看到的。
我们在创建守护进程时,通过关闭编号为0、1、2的文件描述符来避免上面的情况,但有时我们在fork之前已经打开了很多文件,需要一一关闭难免会出错,可以通过设置close_on_exec标志来避免文件描述符没有被关闭。
close_on_exec标志的语义是在fork的子进程中执行exec的时候,会自动清理掉带有close_on_exec标志的文件描述符,具体使用代码如下:
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("fd", O_RDONLY|O_CLOEXEC);
return 0;
}
或者:
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("fd", O_RDONLY);
int flags = fcntl(fd, F_GETFD);
flags |= FD_CLOEXEC;
fcntl(fd, F_SETFD, flags);
return 0;
}
文件锁
在线程同步中我们介绍过互斥锁、读写锁和自旋锁,在文件操作中也有用于同步的文件锁。
如果没有文件锁,在多个进程或线程读写文件时,变化记录在内存中,磁盘上文件的最后状态只取决于最后一个写进程/线程。为了解决这种问题,Linux引入了文件锁,文件锁分为读取锁和写入锁,加锁的规则类似于读写锁,读取锁可以共享,写入锁只能独占。
文件锁通常通过fcntl函数实现,下面是与读写锁相关的宏。
F_GETLK 表示当前是否加文件锁及锁的属性。
F_SETLK 设置锁的属性
F_SETLKW 阻塞方式设置锁的属性
F_RDLCK 读取锁
F_WRLCK 写入锁
F_UNLCK 解锁
struct flock是文件锁的具体结构:
struct flock{
short l_type; //锁的类型
off_t l_start; //相对l_whence的偏移量
short l_whence; //基准位置
off_t l_len; //加锁区域长度
pid_t l_pid; //加锁的进程号
}
可以看到文件锁的粒度是可以由应用程序控制的,最小粒度为字节。