文件描述符:
文件描述符是一个非负整数,所有打开的文件都通过文件描述符引用。
按照惯例:0(STDIN_FILENO)与进程的标准输入关联,1(STDOUT_FILENO)与标准输出关联,2(STDERR_FILENO)与标准错误输出关联。
文件偏移量
每个打开的文件都有一个与其关联的“当前文件偏移量”,用以度量从文件开始处计算的字节数。当打开一个文件时,除非指定O_APPEND,否则偏移量设置为0。lseek仅将当前的文件偏移量记录在内核中,不引起任何I/O操作,然后偏移量用于下一次读或者写。
空洞
文件偏移量大于文件的长度,在下一次对该文件的写中将加长该文件,并在文件中构成一个空洞。位于文件中没有写过的字节都为0
原子操作
任何一个需要多个函数调用的操作都不是原子操作,因为在多个函数调用之间,内核可能会临时挂起进程,这时若有其他的进程对同一个文件进行操作就会造成不可预知的后果。
原子操作:是由多步组成的操作,要么执行完全部的步骤,要么一步都不执行。
unix系统提供的原子操作:
ssize_t pread(int fileds, void* buf, size_t nbytes,off_t offset);
size_t pwrite(int fileds, const void* buf, size_t bytes, off_t offset);
sync,fsync,fdatasync
- 延迟写:数据写入文件时,内核先是将数据复制到一个缓冲区中,如果缓冲区未被写满,则等待其写满或内核需要重用该缓冲区时才将该缓冲区排入输出队列,待其到达队首,才进行实际的I/O操作。
延迟写减少了磁盘读写次数,但也降低了内容跟新速度。当系统发生故障时,延迟写可能会造成文件更新内容的丢失。
为了保证文件系统和缓冲区中内容的一致性:
- sync()
将所有修改过的块缓冲区排入写队列,然后马上返回。 - fsync(int fd)
只对fd指定的文件起作用,并等待写操作结束。同时还会更新文件的属性 - fdatasync(int fd)
和fsync类似,但不会影响文件属性,只影响文件的数据部分。
记录锁
若有时两个进程一起同时编辑某个文件,那么文件的状态将取决于写该文件的最后一个进程。
fcntl记录锁
int fcntl(int fd, int cmd, ....);
....
struct flock
{
short l_type;
//锁类型:F_RDLCK(共享读锁),F_WRLCK(独占写锁),F_UNLCK(解锁一个区域)
off_t l_start;
short l_whence;
//加锁或者解锁区域的起始字节偏移量
off_t l_len;
//区域字节长度,若为0,则表示锁的区域从其起点开始直到最大可能偏移量为止
pid_t l_pid;
//持有进程的ID
}
//为了锁整个文件,可以设置l_start和l_whence,使锁的起点在文件起始处,并且l_len为0。
共享读锁:多个进程在一个给定的字节上可以有一把共享的读锁。
独占写锁:在一个给定字节上只能由一个进程独占的一把写锁。
如果在一个给定字节上已经有一把或者多把读锁,则不能在该字节上再加写锁。
如果在一个字节上已经有一把独占性的写锁,则不能再加任何读锁。
加读锁时,文件描述符必须读打开。
加写锁时,文件描述符必须写打开。
fcntl函数的三个命令:
-
F_GETLK
判断flockptr所描述的锁是否会被另外的一把锁所阻塞。 -
F_SETLK
设置由flockptr所描述的锁。若不允许,则出错返回。 -
F_SETLKW
F_SETLK的阻塞版本
死锁
如果两个进程相互等待对方持有并且锁定的资源时,则这两个进程就处于死锁状态。检测到死锁时,内核必须选择一个进程接收出错返回。
锁的隐含继承和释放
-
当一个进程终止时,它所建立的锁全部释放。
-
当进程关闭一个文件描述符时,该进程通过该描述符引用的文件上的锁都会被释放。
-
由fork产生的子进程不继承父进程所设置的锁。
-
在执行exec后,新程序可以继承原执行程序的锁。
守护进程用文件锁加锁,可以保证只有唯一的一个该进程。
int lockfile(int fd)
{
struct flock f1;
f1.l_type = F_WRLCK;
f1.l_start = 0;
f1.l_whence = SEEK_SET;
f1.l_len = 0;
return(fcntl(fd,F_SETLK,&f1));
}
网友评论