UNIX环境高级编程-第14章- 高级 I/O - 一

14.2 非阻塞I/O

非阻塞 I/O 使我们可以调用 open、write 和 read 这样的 I/O 操作,并使这些操作不会永远阻塞。如果这种操作不能完成,则立即出错返回,表示该操作若继续执行将阻塞。
对于一个给定的文件描述符由以下两种方法可以对其指定非阻塞 I/O:
(1)若调用 open 获得描述符,则可指定 O_NONBLOCK 标志;

(2)对已打开的描述符,可以使用 fcntl,由该函数打开 O_NONBLOCK 文件状态标志;

测试程序:

#include "apue.h"  
#include <fcntl.h>  
  
void set_fl(int fd, int flags);  
void clr_fl(int fd, int flags);  
  
char buf[500000];  
  
int main(void)  
{  
    int ntowrite, nwrite;  
    char *ptr;  
  
    ntowrite = read(STDIN_FILENO, buf, sizeof(buf));  
    fprintf(stderr, "read %d bytes.\n",ntowrite);  
  
    set_fl(STDOUT_FILENO, O_NONBLOCK);  
  
    ptr = buf;  
    while(ntowrite > 0)  
    {  
        errno = 0;  
        nwrite = write(STDOUT_FILENO, ptr, ntowrite);  
        fprintf(stderr, "nwrite = %d, errno = %d\n", nwrite, errno);  
  
        if(nwrite > 0)  
        {  
            ptr += nwrite;  
            ntowrite -= nwrite;  
        }  
    }  
    clr_fl(STDOUT_FILENO, O_NONBLOCK);    
    exit(0);  
}    
//设置文件描述符标志  
void set_fl(int fd, int flags)  
{  
    int val;  
    //获取文件描述符标志  
    if((val = fcntl(fd, F_GETFL, 0)) < 0)  
        err_sys("fcntl F_GETFL error");  
    val |= flags;//添加描述符标志flags  
    //设置文件描述符标志  
    if(fcntl(fd, F_SETFL, val) < 0)  
        err_sys("fcntl F_SETFL error");  
}  
//清除文件描述符标志  
void clr_fl(int fd, int flags)  
{  
    int val;  
    //获取文件描述符标志  
    if((val = fcntl(fd, F_GETFL, 0)) < 0)  
        err_sys("fcntl F_GETFL error");  
    val &= ~flags;//清除描述符标志flags  
    //设置文件描述符标志  
    if(fcntl(fd, F_SETFL, val) < 0)  
        err_sys("fcntl F_SETFL error");  
}  

输出结果:

[root@localhost 14]# ./a.out </root/apue/14/14-1.c > temp.file
read 1497 bytes.
nwrite = 1497, errno = 0
[root@localhost 14]# ll
总计 16
-rw-r--r-- 1 root root 1497 01-08 15:5914-1.c
-rwxr-xr-x 1 root root 7416 01-08 15:59a.out
-rw-r--r-- 1 root root 1497 01-08 16:04temp.file
[root@localhost 14]#

fcntl 函数说明

/* 
*fcntl函数 
*功能:操纵文件描述符,设置已打开的文件的属性*/  
int fcntl(int fd, int cmd, ... /* arg */ );  
/*说明: 
*cmd的取值可以如下: 
*复制文件描述符 
*F_DUPFD (long) 
*设置/获取文件描述符标志 
*F_GETFD (void) 
*F_SETFD (long) 
*设置/获取文件状态标志 
*F_GETFL (void) 
*F_SETFL (long) 
*获取/设置文件锁 
*F_GETLK 
*F_SETLK,F_SETLKW 
*/  

 14.3 记录锁

当多个进程在编辑同一个文件时,在 UNIX 系统中,文件的最后状态取决于写该文件的最后一个进程,但是进程必须要确保它正在单独写一个文件,所以需要用到记录锁机制。

记录锁的功能:当一个进程在读或修改文件的某一部分时,它可以阻止其他进程修改同一个文件区,记录锁也称为字节范围锁,因为它锁定的只是文件中的一个区域或整个文件。

fcntl 记录锁

        记录锁可以通过 fcntl函数进行控制,该函数的基本形式如下:

/* fcntl记录锁 */  
/* 
 * 函数功能:记录锁; 
 * 返回值:若成功则依赖于cmd的值,若出错则返回-1; 
 * 函数原型: 
 */  
#include <fcntl.h>  
int fcntl(int fd, int cmd, .../* struct flock *flockptr */);  
/* 
 * 说明: 
 * cmd的取值可以如下: 
 * F_GETLK              获取文件锁 
 * F_SETLK、F_SETLKW    设置文件锁 
 * 第三个参数flockptr是一个结构指针,如下: 
 */  
struct flock  
{  
    short l_type;       /* F_RDLCK, F_WRLCK, F_UNLCK */  
    off_t l_start;      /* offset in bytes, relative to l_whence */  
    short l_whence;     /* SEEK_SET, SEEK_CUR, SEEK_END */  
    off_t l_len;        /* length, in bytes; 0 means lock to EOF */  
    pid_t l_pid;        /* returned with F_GETLK */  
};  

对 flock 结构如下说明:

锁的类型:F_RDLCK (共享读锁)、F_WRLCK(独占性写锁)或 F_UNLCK(解锁);

加锁或解锁的区域的起始字节偏移量由 l_start 和 l_whence 两者决定,其中 l_whence 的参数和 lseek 函数的 whence 参数一样;l_start 是相对偏移量,l_whenc 是相对偏移量的起点,SEEK_SET(文件起始位置), SEEK_CUR(文件当前位置), SEEK_END(文件尾端);

区域的长度由 l_len 决定;若 l_len 为0,则表示锁的区域从其起点(由 l_start 和 l_whence 两者决定)开始直到最大可能偏移量为止;

该区域可以在当前文件的尾端处开始或越过尾端处开始,但是不能在文件的起始端之前开始;

        共享读锁和独占性写锁的基本规则是:多个进程在一个给定的字节上可以有一把共享读锁,但是在一个给定的字节上只能有一个进程独用一把写锁。若在一个给定的字节上已经有一把或多把读锁,则不能在该字节上再加上写锁;若在一个给定的字节上已经有一把独占性的写锁,则不能再对其加任何的读锁。这些规则如下表所示:


        上面的兼容性规则适用于不同进程提出的请求,并不适用于单个进程提出的多个锁请求;若是一个进程对一个文件区间已经加上一把锁,后来该进程又企图在同一个文件取件加上另一把锁,那么新锁会替换掉老锁。

        加读锁时文件描述符必须是以读打开的,加写锁时文件描述符必须是写打开的。以下是 fcntl 函数的三种命令:

F_GETLK:测试能否加锁。判断由 flockptr 所描述的锁是否会被另外一把锁所排斥。如果存在一把锁,它阻止创建由 flockptr 所描述的锁,则把该现存锁的信息写到 flockptr 指向的结构中,如果不存在这中情况,则除了将 l_type 设置为 UNLCK 之外,flockptr 所指向结构的其他信息保持不变。

F_SETLK:设置锁,在锁已经被占用的情况下,马上返回错误。设置由 flockptr 所描述的锁,如果试图建立一把锁(读锁或者写锁),而按上述兼容性规则不能允许,则 fcntl立即出错返回,此时 errno 设置为 EACCES 或 EAGAIN。此命令也用来消除由 flockpt r说明的锁(l_type 为 UNLOCK)。

F_SETLKW: 设置锁,如果锁被其他进程占用,则阻塞。这是F_SETLK 的阻塞版本,如果因为当前在所请求区间的某个部分另一个进程已经有一把锁,因而按兼容性规则由 flockptr 所请求的锁不能被创建,则使调用进程休眠,如果请求创建的锁已经可用或者休眠由信号终端,则该进程被唤醒。

锁的隐含继承和释放

        关于记录锁的自动继承和释放有以下三条规则:

(1)锁与进程和文件两方面相关:当一个进程终止时,它所建立的锁全部释放;第二:任何时候关闭一个描述符时,则该进程通过这一描述符可以引用的文件上的任何一把锁都被释放;则必须执行以下步骤:

fd1 = open(pathname, ...);

read_lock(fd1,...);

fd2 = dup(fd1);

close(fd2);

(2)由 fork 产生的子进程不继承父进程所设置的锁;如果父进程得到一把锁,然后fork出子进程,则子进程被视为另一个进程,它从父进程处继承过来的任一描述符,不能继承父进程的锁。因为锁的作用就是阻止多个进程同时写同一个文件。

(3)在执行 exec 后,新程序可以继承原执行程序的锁;但是,如果对一个文件描述符设置了 close-on-exec 标志,那么当作为exec 的一部分关闭该文件描述符时,对相应文件的所有锁都被释放了。

测试程序:

#include <fcntl.h>  
#include "apue.h"  
  
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len);  
int lock_test(int fd, int type, off_t offset, int whence, off_t len);  
  
int main(void)  
{  
    int fd, tmp;  
    pid_t pid;  
    pid = getpid();  
    printf("pid: %d\n", pid);  
  
    fd = open("lock.txt", O_RDWR);  
    if(fd < 0)  
        err_sys("open file error");  
  
    tmp = lock_reg(fd, F_SETLK, F_WRLCK, 4, SEEK_SET, 1);  
    if(tmp < 0)  
        err_sys("F_SETLK error");  
    else  
        printf("F_SETLK success\n");  
  
    sleep(3);  
    tmp = lock_test(fd, F_WRLCK, 3, SEEK_SET, 2);  
    if(tmp == 0)  
        printf("not lock\n");  
    else  
        printf("locked\n");  
    close(fd);  
    exit(0);  
}  
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)  
{  
    struct flock lock;  
  
    lock.l_len = len;  
    lock.l_start = offset;  
    lock.l_type = type;  
    lock.l_whence = whence;    
    return(fcntl(fd, cmd, &lock));  
}  
int lock_test(int fd, int type, off_t offset, int whence, off_t len)  
{  
    struct flock lock;  
  
    lock.l_len = len;  
    lock.l_start = offset;  
    lock.l_type = type;  
    lock.l_whence = whence;    
    if(fcntl(fd, F_GETLK, &lock) < 0)  
        err_sys("fcntl error");  
    if(lock.l_type == F_UNLCK)  
        return(0);  
    return(lock.l_pid);  
}  

输出结果:

[root@localhost 14]# ./a.out
pid: 8424
F_SETLK success
not lock
[root@localhost 14]#

上面显示not lock的解释:

首先看F_GETLK:测试能否加锁。判断由 flockptr 所描述的锁是否会被另外一把锁所排斥。如果存在一把锁,它阻止创建由 flockptr 所描述的锁,则把该现存锁的信息写到 flockptr 指向的结构中,如果不存在这中情况,则除了将 l_type 设置为 UNLCK 之外,flockptr 所指向结构的其他信息保持不变。

F_GETLK命令的定义说明,返回信息指示是否有现存的锁阻止调用进程设置它自己的锁。它并不能测试自己是否在文件的某一部分持有一把锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值