UNIX环境C语言编程(11)-高级IO

1、非阻塞IO

一些操作会导致调用进程阻塞,如读取没有数据的管道、写入没有空间的管道
这种行为并不总是我们期望的,有时我们需要非阻塞 IO
操作不能完成时,函数调用将立即返回,同时设置 errno :操作将阻塞
指定非阻塞的 2 种方式:
1 、调用 open 时,指定 O_NONBLOCK 标志
2 、使用 fcntl 设置一个给定描述符的阻塞标志

 

2、记录锁(文件锁)

#include < fcntl.h >
int fcntl ( int filedes , int cmd , ... /* struct flock * flockptr */ );
cmd 参数取值: F_GETLK, F_SETLK, F_SETLKW
struct flock {
    short  l_type /* F_RDLCK, F_WRLCK, or F_UNLCK */
    off_t   l_start /* offset in bytes, relative to l_whence */
    short  l_whence /* SEEK_SET, SEEK_CUR, or SEEK_END */
    off_t   l_len /* length, in bytes; 0 means lock to EOF */
    pid_t   l_pid /* returned with F_GETLK */
};
锁类型:读锁与写锁的限制关系
为得到一个读锁,描述符必须以读方式打开;为得到一个写锁,以写方式打开
死锁的检测:内核会检查死锁的情况,让其中一个进程返回错误,另外一个进程成功
允许文件锁越过文件结尾

 

3、记录锁(续)锁的隐含继承与释放

1 、锁与进程和文件两者关联

            进程结束后,全部文件锁释放

            任何一个描述符被关闭后,当前进程施加在这个文件上的全部锁释放

2 、锁不会被 fork 产生的子进程继承
3 、通过 exec 执行一个新程序时,锁被继承

            但是如果设置了描述符的close-on-exec标志,那么exec之后,锁被释放

 

4、记录锁(续)--建议锁与强制锁

如果所有进程都按照协商一致的方式加锁访问文件,那么建议锁完全能够胜任
强制锁机制中,内核检查每一个 open read write ,核实进程没有违背被访问文件上施加的锁
注意不是所有的操作系统都支持强制锁
如何激活强制锁?打开 设置 - -ID 位,关闭 组执行
表现为: - rwx -r- S ---
关注一下其中的大写 S ,注意与小写 s 的区别
例子,程序为文件施加强制锁,然后换个窗口用 cat 命令查看文件,现象如何?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>

int lck(int fd)
{
    struct flock lck;

    lck.l_type = F_WRLCK;
    lck.l_start = 0;
    lck.l_whence = SEEK_SET;
    lck.l_len = 0;

    if( fcntl(fd, F_SETLK, &lck) < 0 )
    {
        return(-1);
    }

    return(0);
}

int main(int argc, char *argv[])
{
    int             fd;

    struct stat     statbuf;

    if( argc != 2 )
    {
        fprintf(stderr, "usage: %s filename\n", argv[0]);
        exit(1);
    }
    if( (fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0777)) < 0 )
    {
        perror("open");
        exit(0);
    }
    if( write(fd, "abcde\n", 6) != 6 )
    {
        perror("write");
        exit(0);
    }

    /* turn on set-group-ID and turn off group-execute */
    if( fstat(fd, &statbuf) < 0 )
    {
        perror("fstat");
        exit(0);
    }
    if( fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0 )
    {
        perror("fchmod");
        exit(0);
    }

    if( lck(fd) < 0 )
    {
        perror("fcntl");
        close(fd);
        exit(0);
    }

    if( fork() == 0 )
    {
        if( lck(fd) < 0 )
        {
            perror("child fcntl");
            close(fd);
            exit(0);
        }
        exit(0);
    }

    sleep(30);
}


5、IO多路复用技术

思考:如何兼顾读取两个描述符?
1 、轮询,浪费 CPU 时间,在多任务系统中尽量避免
2 、多进程,需要考虑进程同步,增加编程复杂度
3 、异步 IO ,不是所有的系统都支持,另外避免不了轮询
#include <sys/ select.h >
int select ( int maxfdp1, fd_set *restrict readfds , fd_set *restrict writefds , fd_set *restrict exceptfds , struct timeval *restrict tvptr );
调用时,我们可以告知内核:
我们关心哪些描述符、对每个描述符关注哪些情况、想等待多长时间
返回时,内核告知我们:
处于就绪状态的描述符总数、每种情况下各有哪些描述符就绪

 

6、select函数

首先关注最后一个参数: struct timeval *restrict tvptr ,指定超时时间
struct timeval {
    long tv_sec /* seconds */
    long tv_usec /* and microseconds */
};
考虑 3 种情形:
1 tvptr == NULL ,无限期等待,直至成功返回或被信号中断
2 tvptr->tv_sec == 0 && tvptr->tv_usec == 0 ,压根不等待,不阻塞
3 tvptr -> tv_sec != 0 || tvptr -> tv_usec != 0 ,指定超时时间,超时后 select 返回 0
 
中间的三个参数: fd_set * readfds , * writefds , * exceptfds
每一个都是指针,指向描述符的集合,可以为 NULL (表示不关心对应的情况)
 
4 个相关的宏:
#include <sys/ select.h >
void FD_ZERO ( fd_set * fdset );
void FD_CLR ( int fd , fd_set * fdset );
void FD_SET ( int fd , fd_set * fdset );
int FD_ISSET ( int fd , fd_set * fdset );
第一个参数 int maxfdp1 ,表示:
所有三个集合中最大的描述符 +1 ,换句话说,就是所关心的描述符的数目

 

select 函数的返回值:
1 -1 表示错误发生(捕获到一个信号,此时描述符集合将保持不变)
2 0 表示超时,此时所有描述符集合被清 0
3 )正数,表示准备好的描述符的数目
什么是 准备好
1 )读取 readfds 中的描述符不会阻塞时
2 )写入 writefds 中的描述符不会阻塞时
3 exceptfds 中的描述符发生异常时
4 )三个集合中,普通文件的描述符总是准备好
注意:描述符自身阻塞与否,并不影响 select 是否阻塞
/************************************************
 select多路IO复用,同时检查多个打开的描述符的状态
 ************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>

int main(void)
{
    int    n, nfds;
    char   buf[32] = {0};

    fd_set readfds;
    struct timeval tv;

    FD_ZERO(&readfds);        /* 清空描述符集合        */
    FD_SET(0, &readfds);      /* 添加描述符 0          */
                              /* FD_CLR从集合中删去 fd */

    tv.tv_sec = 5;
    tv.tv_usec = 0;           /* 微秒 */

    printf("Type a word, if you don't in 5 seconds ");
    printf("I'll use \"YES\":");
    fflush(stdout);

    nfds = select(1, &readfds, NULL, NULL, &tv);
    printf("[%d]\n", nfds);

    if( nfds <= 0 )
    {
        strcpy(buf, "YES");
    }
    else
    {
        /* 判断 0是否可读 */
        if( FD_ISSET(0, &readfds) )
        {
            n = read(0, buf, sizeof(buf));
            buf[n > 0 ? n - 1 : 0] = '\0';
        }
    }

    printf("\nThe final word is: %s\n", buf);
    exit(0);
}

了解: poll 函数用另外一种方式实现了多路 IO
#include < poll.h >
int poll ( struct pollfd fdarray [], nfds_t nfds , int timeout);
/**********************************************
 poll多路IO复用,同时检查多个打开的描述符的状态
 **********************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <poll.h>

int main(void)
{
    int    n, nfds;
    char   buf[32] = {0};

    struct pollfd fds;

    printf("Type a word; if you don't in 5 seconds ");
    printf("I'll use \"YES\":");
    fflush(stdout);

    fds.fd = 0;
    fds.events = POLLIN | POLLPRI;
    fds.revents = 0;

    nfds = poll(&fds, 1, 5000);    /* 超时时间单位毫秒 */
    printf("[%d]\n", nfds);

    if( nfds <= 0 )
    {
        strcpy(buf, "YES");
    }
    else
    {
        /* 检查0 是否可读 */
        if( fds.revents & POLLIN )
        {
            n = read(0, buf, sizeof(buf));
            buf[n > 0 ? n - 1 : 0] = '\0';
        }
    }

    printf("\nThe final word is: %s\n", buf);
    exit(0);
}


7、readvwritev函数

#include <sys/ uio.h >
ssize_t readv ( int filedes , const struct iovec * iov , int iovcnt );
ssize_t writev ( int filedes , const struct iovec * iov , int iovcnt );
这两个函数分别允许我们在单个系统调用中读写多个非连续的缓冲区
因为减少了系统调用的次数,性能将得以提升
 
8、 readn writen 函数
这两个函数不是系统自带的
当读取或写入管道、网络、终端时,需要考虑下面两种特性:
1 、读操作可能返回比预期少的数据
2 、写操作可能写入比预期少的数据
所以需要考虑循环处理,示例:
ssize_t             /* Read "n" bytes from a descriptor */
readn(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); /* error, return -1 */
            else
                break;      /* error, return amount read so far */
        }
        else if( nread == 0 )
        {
            break;          /* EOF */
        }

        nleft -= nread;
        ptr += nread;
    }

    return(n - nleft);      /* return >= 0 */
}

ssize_t             /* Write "n" bytes to a descriptor */
writen(int fd, const void *ptr, size_t n)
{
    size_t      nleft;
    ssize_t     nwritten;

    nleft = n;
    while( nleft > 0 )
    {
        if( (nwritten = write(fd, ptr, nleft)) < 0 )
        {
            if( nleft == n )
                return(-1); /* error, return -1 */
            else
                break;      /* error, return amount written so far */
        }
        else if( nwritten == 0 )
        {
            break;
        }

        nleft -= nwritten;
        ptr   += nwritten;
    }

    return(n - nleft);      /* return >= 0 */
}

9、 内存映射 IO
内存映射 IO 技术允许将磁盘上的文件映射到内存中的一个区域,这样:
1 、从内存中读取,相当于读取文件内容
2 、写入内存,相当于写入文件
#include <sys/ mman.h >
void * mmap (void * addr , size_t len , int prot , int flag, int filedes , off_t off);
先看一下后面的图示
参数说明:
addr 指定映射的起始地址,通常指定为 0 ,即让系统自行选择
filedes 对应被映射的文件, len 指定长度, off 指定偏移
prot 指定权限,可以是 PROT_NONE ,或 PROT_READ , PROT_WRITE , PROT_EXEC 的位或组合, 不能超越打开文件时指定的读写模式
flag 指定映射属性, MAP_SHARED 表示写入映射内存直接影响对应的文件, MAP_PRIVATE 表示映射内存是文件的一个副本
 
#include <sys/ mman.h >
int mprotect (void * addr , size_t len , int prot ); # 修改一块已映射内存的访问模式
int msync (void * addr , size_t len , int flags);  # 刷新缓存
参数 flags ,可以指定为 MS_ASYNC MS_SYNC
int munmap ( caddr_t addr , size_t len );  # 取消映射
进程终止时,将自动取消映射;关闭文件描述符并不取消映射
例子,通过内存映射方式实现文件拷贝
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main(int argc, char *argv[])
{
    int         fdin, fdout;
    void        *src, *dst;
    struct stat statbuf;

    if( argc != 3 )
    {
        printf("usage: %s <fromfile> <tofile>\n", argv[0]);
        exit(0);
    }

    if( (fdin = open(argv[1], O_RDONLY)) < 0 )
    {
        printf("can't open %s for reading\n", argv[1]);
        exit(0);
    }

    if( (fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0777)) < 0 )
    {
        printf("can't creat %s for writing\n", argv[2]);
        exit(0);
    }

    if( fstat(fdin, &statbuf) < 0 )   /* need size of input file */
    {
        printf("fstat error\n");
        exit(0);
    }

    /* set size of output file */
    if( lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1 )
    {
        printf("lseek error\n");
        exit(0);
    }
    if( write(fdout, "", 1) != 1 )
    {
        printf("write error\n");
        exit(0);
    }

    if( (src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED )
    {
        printf("mmap error for input\n");
        exit(0);
    }

    if( (dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, 0)) == MAP_FAILED )
    {
        printf("mmap error for output\n");
        exit(0);
    }

    memcpy(dst, src, statbuf.st_size); /* does the file copy */
    exit(0);
}

备注: 进程间通信 一章,将再次回顾内存映射 IO 技术,用以提供进程间内存共享
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值