UNIX高级环境编程读书笔记(chapter14)

来源:互联网 发布:淘宝客推广在哪里设置 编辑:程序博客网 时间:2024/06/05 16:45

第十四章  高级IO

一、引言

       本章内容包括非阻塞IO、记录锁、系统V流机制、I/O多路转接(select 和 poll函数)、readv和writev函数以及存储映射I\O,这些都称为高级I/O。

二、非阻塞I/O

       非阻塞I/O使我们可以调用open、read和write这样的I/O操作,并使这些操作不会永远阻塞。如果这种操作不能完成,则调用立即出错返回,表示该操作如继续执行将阻塞。

       对于一个给定的描述符有两种方法对其指定非阻塞I/O:

       (1)如果调用open获得描述符, 则可指定O_NONBLOCK标志;

       (2)对于一个已经打开的描述符,则可调用fcntl,由该函数打开O_NONBLOCK文件状态标志。

实例:长的非阻塞write

#include "apue.h"#include <errno.h>#include <fcntl.h>void set_fl(int fd, int flags);void clr_fl(int, int);char buf[50000];intmain(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;    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;    if (fcntl(fd, F_SETFL, val) < 0)    {        err_sys("fcntl F_SETFL error");    }}

       此程序是一个非阻塞I/O的实例,它从标准输入读500000字节,并试图将它们写到标准输出上。该程序现将标准输出设置为非阻塞的,然后用for循环进行输出,每次write调用的结果都在标准出错上打印。函数clr_fl类似于set_fl,但与set_fl的功能相反,它清除一个或多个标志位。


三、记录锁

       进程有时需要确保它正在单独写一个文件,为了向进程提供这种功能,商用UINIX系统提供了记录锁机制。

       记录锁的功能是:当一个进程正在读或修改文件的某个部分时,它可以组织其他进程修改同意文件区。

四、fantl记录锁

       int fcntl(int fileds, int cmd, ... /*struct lock *flockptr*/);

       对于记录锁,cmd是F_GETLK, F_SETLK,或F_SETLKW,第三个参数是一个指向flock结构的直针:

              struct flock{

                 short  l_type;

                 off_t   l_start;

                 short  l_whence;

                 off_t   l_len;

                 pid_t   l_pid;

              };

         多个进程在一个给定的自字节上可以有一把共享的读锁,但是一个给定的字节上只能有一个进程独用的一把写锁,进一步而言,如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能对它再加任何读锁。

          F_GETLK:判断由flockkptr所描述的锁是否会被另外一把锁排斥(阻塞)。

          F_SETLK:设置由flockptr所描述的锁。

          F_SETLKW:这是F_SETLK的阻塞版本。

实例:加锁或解锁一个文件区域的函数:


&&#include "apue.h"#include <fcntl.h>intlock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len){    struct flock lock;    lock.l_type = tupe;    lock.l_start = offset;    lock_whence = whence;    lock_l.len = len;    return (fcntl(fd, cmd, &lock));}&&

实例:测试一个锁状态的函数


&&#include "apue.h"#include <fcntl.h>pid_tlock_test(int fd, int type, off_t offset, int whence, off_t len){    struct flock flock;    lock.l_type = type;    lock.l_start = offset;    lock.l_whence = whence;    lock.l_len = len;    if (fcntl(fd, F_GETLK) < 0)    {        err_sys("fcntl error");    }    if (lock.l_type == FUNLCK)    {        return 0;    }    return (lock.l_pid);}&&

实例:死锁检测实例

       如果两个进程相互等待对方持有并且锁定的资源时,则这两个就处于死锁状态。


&&#include "apue.h"#include <fcntl.h>#include "tellwait.h"int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len){    struct flock lock;    lock.l_type = type;    lock.l_start = offset;    lock.l_whence = whence;    lock.l_len = len;    return (fcntl(fd, cmd, &lock));}static void lockabyte(const char *name, int fd, off_t offset){    if (writew_lock(fd, offset, SEEK_SET, 1) < 0)    {        err_sys("%s: writew_lock error", name);    }    printf("%s: got the lock, byte %ld\n", name, offset);}int main(void){    int fd;    pid_t pid;    if ((fd = creat("templock", FILE_MODE)) < 0)    {        err_sys("creat error");    }    if (write(fd, "ab", 2) != 2)    {        err_sys("write error");    }    TELL_WAIT();    if ((pid = fork()) < 0)    {        err_sys("fork error");    }    else if (pid == 0)    {        lockabyte("child", fd, 0);        TELL_PARENT(getppid());        WAIT_PARENT();        lockabyte("child", fd, 1);    }    else    {        lockabyte("parent", fd, 1);        TELL_CHILD(pid);        WAIT_CHILD();        lockabyte("parent", fd, 0);    }    exit(0);}&&


五、锁的隐含进程和释放

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

      (1)锁与进程和文件两方面有关。

      (2)由fork产生的子进程不继承父进程所设置的锁。

      (3)在执行exec后,新程序可以继承原执行程序的锁。 


实例:在文件整体上加锁

&&#include <unistd.h>#include <fcntl.h>int lockfile(int fd){    struct flock fl;    fl.l_type = F_WRLCK;    fl.l_start = 0;    fl.l_whence = SEEK_SET;    fl.l_len = 0;    return(fcntl(fd, f_SETLK, &fl));}&&

六:在文件尾端处加锁

       负的长度值表示在指定偏移量之前的字节数。

七、建议性锁和强制性锁

       考虑数据库访问例程库。如果该库中所有函数都以一致的方法处理记录锁,则称使用这些函数访问数据库的任何进程集为合作进程。

        强制性锁使内核对每一个open、read和write系统调用都进行检查,检查调用进程对正在访问的文件受否违背了某一把锁的作用。

实例:确定是否支持强制性锁

&&#include "apue.h"#include <errno.h>#include <fcntl.h>#include <sys/wait.h>#include "tellwait.h"int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len);void set_fl(int fd, int flags);void clr_fl(int fd, int flags);int main(int argc, char *argv[]){    int fd;    pid_t pid;    char buf[5];    struct stat statbuf;    if (argc != 2)    {        fprintf(stderr, "uasge: %s filename\n", argv[0]);        exit(1);    }    if ((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)    {        err_sys("open error");    }    if (write(fd, "abcdef", 6) != 6)    {        err_sys("write error");    }    if (fstat(fd, &statbuf) < 0)    {        err_sys("fstat error");    }    if (fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)    {        err_sys("fchmod error");    }    TELL_WAIT();    if ((pid == fork()) < 0)    {        err_sys("fork error");    }    else if (pid == 0)    {        WAIT_PARENT();        set_fl(fd, O_NONBLOCK);        if (read_lock(fd, 0, SEEK_SET, 0) != -1)        {            err_sys("child: read_lock succeeded");        }        printf("read_lock of already-locked region returns %d\n", errno);        if (lseek(fd, 0, SEEK_SET) == -1)        {            err_sys("lseek error");        }        if (read(fd, buf, 2) < 0)        {            err_ret("read failed (mandatory locking works)");        }        else        {            printf("read OK (no mandatory locking), buf = %2.2s\n", buf);        }    }    else    {        if (write_lock(fd, 0, SEEK_SET, 0) < 0)        {            err_sys("write_lock error");        }        TELL_CHILD(pid);        if (waitpid(pid, NULL, 0) < 0)        {            err_sys("waitpid error");        }    }    exit(0);}int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len){    struct flock lock;    lock.l_type = type;    lock.l_start = offset;    lock.l_whence = whence;    lock.l_len = len;    return (fcntl(fd, cmd, &lock));}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;    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;    if (fcntl(fd, F_SETFL, val) < 0)    {        err_sys("fcntl F_SETFL error");    }}&&



八、STREAMS

          流是系统V提供的构造内核设备驱动程序和网络协议包的一种通用方法。

          流在用户进程和设备驱动程序之间提供了一条全双工通路。流无需和实际硬件设备直接会话,流也可以用来构造伪设备驱动程序。

          在流首之下可以压入处理模块。这可以用ioctl命令实现。用第三章说明的函数访问流,它们是:open、close、read、write和ioctl。在VR3内核中增加了3个支持流的新函数(getmsg、putmsg和poll),在SVR4中又加了两个处理流内不同优先级波段休息的函数(getpsmg和putpmasg)。

          (1)STREAMS的所有输入和输出都基于消息。流首和用户进程使用read、write、ioctl、getmsg、getpmsg、             putpmsg和putmsg交换消息。在流首、各处理模块和设备驱动程序之间,消息可以顺流而下,也可以逆流而             上。控制信息和数据由strbuf结构指定:

          struct strbuf

          {

              int  maxlen;

              int  len;

              char *buf;

          };

         在我们所使用的函数中,只涉及三种消息类型,它们是:

          M_DATA (I/O的用户数据);

          M_PROTO(协议控制信息);

          M_PCPROTO(最高优先级协议控制信息)。

         (2)putymsg和putpmsg

          用于将STREAMS消息写至流中,这两个函数的区别是:后者允许对指定的消息指定一个优先级波段。

         (3)STREAMS ioctl操作

          ioctl函数能做其它I/O函数不处理的事情,STREAMS系统就继承了这种传统。

          实例:isastream函数

           

#include <stropts.h>&&#include <unistd.h>int isastream(int fd){    return(ioctl(fd, I_CANPUT, 0) != 1);}&&

            实例:测试isastream函数

&&#include "apue.h"#include <fcntl.h>#include <stropts.h>#include <unistd.h>int isastream(int fd){    return(ioctl(fd, I_CANPUT, 0) != -1);}int main(int argc, char *argv[]){    int i, fd;    for (i = 1; i < argc; i++)    {        if ((fd = open(argv[i], O_RDONLY)) < 0)        {            err_ret("%s: can't open", argv[i]);            continue;        }        if (isastream(fd) == 0)        {            err_ret("%s: not a stream", argv[i]);        }        else        {            err_msg("%s: streams device", argv[i]);        }    }    exit(0);}&&
编译APUE:apue.2e
http://www.cnblogs.com/feiling/archive/2012/02/15/2353286.html

 实例:列表流中的模块名

          如果ioctl的参数request是L_LIST, 则系统返回已压入该流所有模块的名字,包括最顶端的驱动程序。 

&&#include "apue.h"#include <fcntl.h>#include <stropts.h>int main(int argc, char *argv[]){    int                 fd, i, nmods;    struct str_list     list;    if (argc != 2)    {        err_quit("usage: %s <pathname>", argv[0]);    }    if ((fd = open(argv[1], O_RDONLY)) < 0)    {        err_sys("can't open %s", argv[i]);    }    if (isastream(fd) == 0)    {        err_quit("%s is not a stream", argv[1]);    }    if ((nmods = ioctl(fd, I_LIST, (void *) 0)) < 0)    {        err_sys("I_LIST error for nmods");    }    printf("#modules = %d\n", nmods);    list.sl_modlist = calloc(nmods, sizeof(struct str_mlist));    if (list.sl_modlist == NULL)    {        err_sys("calloc error");    }    list.sl_nmods = nmods;    if (ioctl(fd, I_LIST, &list) < 0)    {        err_sys("I_LIST error for list");    }    for (i = 1; i <= nmods; i++)    {        printf(" %s: %s\n", (i == nmods) ? "driver" : "module", list.sl_modlist++->l_name);    }    exit(0);}&&


        (4)写(write)至STREAMS设备

        (5)写模式

        可以用两个ioctl命令取得和设置一个流的写模式。如果将request设置为I_GWRPORT,第三个参数设置为指向一         个整型变量的指针,则该流的当前写模式在该整型量中返回。

        (6)getmsg和getpmsg函数

        使用read、getmsg和getpmsg函数从流首读STREAMS消息。

        (7)读模式

        实例:用getmsg将标准输入复制到标准输出

         

&&#include "apue.h"#include <stropts.h>#define BUFFSIZE 4096int main(void){    int             n, flag;    char            ctlbuf[BUFFSIZE], datbuf[BUFFSIZE];    struct strbuf   ctl,dat;    ctl.buf = ctlbuf;    ctl.maxlen = BUFFSIZE;    dat.buf = datbuf;    dat.maxlen = BUFFSIZE;    for ( ; ; )    {        flag = 0;        if ((n = getmsg(STDIN_FILENO, &ctl, &dat, &flag)) < 0)        {            err_sys("getmsg error");        }        fprintf(stderr, "flag = %d, ctl.len = %d, dat.len = %d\n", flag, ctl.len, dat.len);        if (dat.len == 0)        {            exit(0);        }        else if (dat.len > 0)        {            if (write(STDOUT_FILENO, dat.buf, dat.len) != dat.len)            {                err_sys("write error");            }        }    }}&&

九、I/O多路转接

       poll、pselect和select这三个函数使我们能够执行I/O多路转接。

       在所有依从POSIX的平台上,select函数使我们可以执行I/O多路转接。

       poll函数类似于select,但是其程序员接口则有所不同。

十、readv和writev函数

       readv和write函数用于在一次函数调用中读、写多个非连续缓冲区。

十一、readn和writen函数

       readn和writen的功能是读、写指定的n字节数据,并处理返回值小于要求值的情况。这两个函数只是按需多次调用read和write直至读、写了n字节数据。

实例:readn和writen函数

&&#include "apue.h"ssize_treadn(int fd, void *ptr, size_t n){    size_t      nleft;    ssize_t     nread;    nleft = n;    while (nleft > 0)    {        if ((nread = read(fd, ptr, nleft)) < 0)        {            if (nleft == n)            {                return(-1);            }            else            {                break;            }        }        else if (nread == 0)        {            break;        }        nleft = -nread;        ptr += nread;    }    return(n - nleft);}ssize_twriten(int fd, const void *ptr, size_t n){    size_t      nleft;    ssize_t     nwritten;    nleft = n;    while(n < 0)    {        if ((nwritten = write(fd, ptr, nleft)) < 0)        {            if (nleft == n)            {                return(-1);            }            else            {                break;            }        }        nleft -= nwritten;        ptr += nwritten;    }    return(n - left);}&&


          

十二、存储映射I/O

       存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。与此类似,将数据存入缓冲区,则相应字节就自动的写入文件。这样就可以在不使用read和write的情况下执行I/O.

实力:用存储映射I/O复制一个文件(类似于cp(1)命令)

&&#include "apue.h"#include <fcntl.h>#include <sys/mman.h>int main(int argc, char *argv[]){    int             fdin, fdout;    void            *src, *dst;    struct  stat    statbuf;    if (argc != 3)    {        err_quit("usage: %s <fromfile> <tofile>", argv[0]);    }    if ((fdin = open(argv[1], O_RDONLY)) < 0)    {        err_sys("can't open %s for reading", argv[1]);    }    if ((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)    {        err_sys("can't create %s for writing", argv[2]);    }    if (fstat(fdin, &statbuf) < 0)    {        err_sys("fstat error");    }    if (lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1)    {        err_sys("lseek error");    }    if (write(fdout, "", 1) != 1)    {        err_sys("write error");    }    if ((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED)    {        err_sys("mmap error");    }    if ((dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, 0)) == MAP_FAILED)    {        err_sys("mmap error for output");    }    memcpy(dst, src, statbuf.st_size);    exit(0);}&&






















0 0
原创粉丝点击