高级I/O
1,非阻塞I/O
有些调用可能会使进程永远阻塞,它们可能包括:
.读管道,终端设备或网络设备的数据并不存在时,读操作会使调用者永远阻塞。
.数据不能立即被写到上述同类型文件接受,该写操作也会阻塞。
.在某条件发生之前,打开某种类型的文件,也会被阻塞,例如:打开用写模式打开FIFO,该FIFO没有任何其>他进程用读模式打开时,也会阻塞。
.对已经加上强制性记录锁的文件进行读写。
.某些ioctl操作
.某些进程通信函数:
对于一个给定的描述符有两种方法对其指定非阻塞I/O:
(1)open(fd, O_NONBLOCK)。
(2)对于一个已经打开的描述符,可以用fcntl,来设定非阻塞O_NONBLOCK标志。
2, 记录锁(文件锁)
*记录锁和文件锁
.记录锁的上锁粒度可以很小,可以到字节;而文件锁是对整个文件上锁。
.用fcntl上锁是针对进程间的,不是针对一个进程的线程的。
*记录锁
当两个进程同时编辑一个文件时,其文件的最后状态是由最后一个进程编辑的状态决定的。而记录锁是为了当一个进程修改一个文件的同一区域时,它可以阻止其他进程修改同一文件区域或整个文件。
给文件加锁的方式有很多中,进程间的加锁一般用,fcntl函数。
*建议性锁
int fcntl(int fd, int cmd, /*struct flock *arg*/);
该函数是基于一个打开的描述符。该描述符的打开的属性决定了以后加锁的类型。
*用于记录上锁的cmd参数共有三个值。这三个命令要求第三个参数arg是指向某个flock结构的。
struct flock {
...
short l_type; /* Type of lock: F_RDLCK,
F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock
(F_GETLK only) */
...
};
F_SETLK
获取一个锁(l_type是F_RDLCK或F_WRLCK)或者释放一个锁(l_type是F_UNLCK),该锁的范围基于l_whence,l_start,l_len的值。若该锁无法授与调用进程,该函数就立即返回一个EACCES或EAGAIN。
F_SETLKW
设置一个锁,和F_SETLK不同的是,如果由于原来有一把锁而不能设置,则进程等待到该锁释放,直到可以设置为止。如果在等待期间被一个信号终端,从该信号处理函数返回后,该函数也立即返回-1且设定errno=EINTR。
F_GETLK
检查由arg指向的锁以确定是否有某个已存在的锁会妨碍新锁的授予。如果当前没有这样的锁存在,由arg指向的flock结构的l_type成员就被设置为F_UNLCK。否则,关于这个已存在锁的信息将在由arg指向的flock结构中返回,其中包括持有该锁的进程的进程ID。
*flock结构描述锁的类型,以及待解锁住的字符范围。和lseek一样,起始的字节偏移量作为一个相对偏移量(l_start成员)伴随其解释(l_whence成员)指定的。l_whence成员有三个取值:
SEEK_SET :l_start 相对于文件的开头解释。
SEEK_CUR :l_start 相对于文件的当前字节偏移量解释。
SEEK_END :l_start 相对于文件的末尾解释。
l_len成员指定从该偏移量开始的连续字节数。长度为0表示,从起始偏移量到文件偏移量的最大可能值。
*锁住整个文件的方式
(1)l_whence 成员为SEEK_SET,l_start 成员为0,l_len成员为0。
(2)用lseek把文件指针定位到文件头,然后指定l_whence成员为SEEK_CUR,l_start成员为0,l_len为0。
*加锁的原则
对于文件的任意一个字节,最多只能存在一种类型的锁(读锁或写锁)。而且,一个给定字节可以有多个读出锁,但只能有一个写入锁。
*锁的隐含继承和释放
.当一个进程终止时,它所建立的锁全部释放。
.任何时候关闭一个描述符时,该进程通过这一描述符可以引用的文件上的任何一把锁都被释放。
fd1 = open(filename, ...);
read_lock(fd1, ...);
fd2 = dup(fd1);
close(fd2);
或
fd1 = open(filename, ...);
read_lock(fd1, ...);
fd2 = open(filename, ...);
close(fd2);
在关闭close(fd2)后,在fd1上设置的锁被释放。
.由fork产生的子进程不继承父进程所设置的锁。
.执行exec函数后,新程序可以继承原执行程序的锁,但若设置了close-on-exec标志,那么当作为exec的一部分关闭该文件描述符时,对应的文件的所有锁都被释放。
*记录锁和标准I/O
记录上锁不应该同标准I/O函数一起使用,因为该函数库会执行内部缓冲。所以应该使用write或read。
*用宏来简化fcntl函数的调用
加读锁
#define read_lock(fd, offset, whence, len) /
lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, len)
加阻塞式读锁:
#define readw_lock(fd, offset, whence, len) /
lock_reg(fd, F_SETLKW, F_RDLCK, offset, whence, len)
加写锁
#define write_lock(fd, offset, whence, len) /
lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, len)
加阻塞式写锁
#define write_lock(fd, offset, whence, len) /
lock_reg(fd, F_SETLKW, F_WRLCK, offset, whence, len)
解锁
#define un_lock(fd, offset, whence, len)
lock_reg(fd, F_SETLK, F_UNLCK, offset, whence, len)
加锁测试:是否可以加一把读锁
#define is_read_lockable(fd, offset, whence, len)
!lock_test(fd, F_RDLCK, offset, whence, len)
加锁测试:是否可以加一把写锁
#defiine is_write_lockable(fd, offset, whence, len)
!lock_test(fd, F_WRLCK, offset, whence, len)
用到的函数:
int
lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLCK */
lock.l_start = offset; /* 相对于l_whence的偏移量 */
lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
lock.l_len = len; /* 0 means to EOF */
return (fcntl(fd, cmd, &lock)); //-1 error
}
pid_t
lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLCK */
lock.l_start = offset; /* 相对于l_whence的偏移量 */
lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
lock.l_len = len; /* 0 means to EOF */
if (fcntl(fd, F_GETLK, &lock) == -1) //获取进程原来的锁,若成功把l_type设置成F_UNLCK
return -1;
if (lock.l_type == F_UNLCK) //是否获取成功
return 0;
return(lock.l_pid); //获取失败了,返回原来的进程ID
}
*劝告性锁
若没有对文件的权限做处理,用fcntl加的锁都是建议性锁。它不能防止一个进程写已由另一个进程读锁定的某个文件。这样的程序vi是一个典型的例子,它为打开的文件加的就是劝告性锁,所以你编辑文件的时候别人也可以编辑,但结果是无法意料的。
*强制性锁(mandatory locking)
使用强制性锁后,内核检查每个read和write请求,以验证其操作不会干扰由某个进程持有的某个锁。
.要使用强制性锁,需要满足的条件:
(1) 组成员执行位必须关掉
(2) SGID位必须打开
注意:这样操作需要打开文件的组执行位,和用户执行位,这样才有意义。
*强制性锁的问题
使用强制性锁仍然存在问题,主要是当有多个进程需要读或写一个文件时。在强制性锁的解锁的空档有可能让其他进程有机可乘,从而操作该文件,使得结果出现错误。
*记录锁的读锁和写锁的优先级
.如果原来是读锁,现在有一个读锁和一个写锁的请求,那么一般会首先满足读锁让写锁等待。
.如果原来是写锁,一般是顺序给予锁,按照FIFO的顺序。
.看具体的系统而定,没有保证。
*锁的应用
.守护进程中确保只有一个进程实例在运行。
write_lock(pidfd, 0, SEEK_SET, 0) //开始给整个文件加上写锁,并保持
.协同进程间的劝告性锁,也就是说需要有权限和逻辑上的控制,不能只靠锁。