APUE读书笔记-第十四章-高级I/O

非阻塞I/O

对低速设备的I/O操作可能会使进程永久阻塞,这类系统调用主要有如下情况:

(1)如果数据并不存在,则读文件可能会使调用者永远阻塞(例如读管道、终端设备和网络设备)。
(2)如果数据不能立即被接受,则写这些同样的文件也会使调用者永远阻塞;
(3)在某些条件发生之前,打开文件会被阻塞(例如以只写方式打开一个FIFO,那么在没有其他进程已用读方式打开该FIFO时);
(4)对已经加上强制性锁的文件进行读、写;
(5)某些ioctl操作;
(6)某些进程间通信函数;
非阻塞I/O调用open、read和write等I/O操作函数使上述的慢速系统调用在不能立即完成的情况下,立即出错返回。

对一个给定的描述符有两种方法设置其为非阻塞:

(1)如果是调用open以获得该描述符,则可指定O_NONBLOCK标志;
(2)对于已经打开的一个描述符,则可调用fcntl打开O_NONBLOCK文件状态标志(注意:设置文件状态标志的方法)。

记录锁(字节范围锁)

功能:当第一个进程正在读或修改文件的某个部分时,使用记录锁可以阻止其他进程修改同一文件区。记录锁锁定的是文件中的一个区域(也可能是整个文件)。

函数原型

#include  <fcntl.h>
int fcntl(int fd, int cmd, .../*struct flock *flockptr*/)
struct flock  
  {  
     short int l_type;   /* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK.  */  
    short int l_whence;  /* SEEK_SET, SEEK_CUR, SEEK_END */  
    off_t l_start;       /* offset in bytes, relative to l_whence */  
    off_t l_len;         /* length in bytes; 0 means lock to EOF.  */  
    pid_t l_pid;         /* Process holding the lock.  */  
  };  

函数lock_test

#include "apue.h"
#include <fcntl.h>

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 or F_WRLCK */
    lock.l_start = offset;  /* byte offset, relative to l_whence */
    lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
    lock.l_len = len;       /* #bytes (0 means to EOF) */

    if (fcntl(fd, F_GETLK, &lock) < 0)
        err_sys("fcntl error");

    if (lock.l_type == F_UNLCK)
        return(0);      /* false, region isn't locked by another proc */
    return(lock.l_pid); /* true, return pid of lock owner */
}

进程不能用lock_test来测试自己是否在文件的某一部分持有一把锁,F_GETLK命令的定义说明,返回信息指示是否有现有的锁阻止调用进程设置它自己的锁。因为 F_SETLK 和 F_SETLKW 总是替换调用进程现有的锁(若已存在),所以调用进程决不会阻塞在自己持有的锁上,所以F_GETLK 命令决不会报告调用进程自己持有的锁。

锁的隐含继承与释放

(1)锁与进程和文件相关联:当一个进程终止时,它所建立的锁全部释放。当文件描述符关闭时,则该文件描述符上由某个进程设置的锁也会释放
(2)由fork产生的子进程不继承父进程所设置的锁
(3)在执行exec后,新程序可以继承原执行程序的锁,但如果一个文件描述符设置了执行时关闭标志,那么当作为exec的一部分关闭该文件描述符时,将释放响应文件的所有锁

I/O多路转接

概述:将我们感兴趣的描述符列表传给一个函数,该函数直到这些描述符中的一个已经准备好I/O时才返回。
select 和 poll 函数基本用法点击这里

异步I/O

概述:利用这种技术,进程告诉内核,当描述符准备好进行I/O时,用一个信号通知它。

AIO控制块

struct aiocb  
{  
  int              aio_fildes;                   /* file desriptor */ 
  off_t            aio_offset;                   /* file offset for I/O */
  volatile void   *aio_buf;                      /* buffers for I/O */  
  size_t           aio_nbytes;                   /* numbers of bytes to transfer */ 
  int              aio_reqprio;                  /* priority */  
  struct sigevent  aio_sigevent;                 /* signal information*/ 
  int              aio_lio_opcode;               /* operation for list I/O*/  

sigevent 事件

struct sigevent {  
    int            sigev_notify;                  /* notify type*/
    int            sigev_signo;                   /* signal number */
    union sigval   sigev_value;                   /* notify argument */
    void (*sigev_notrify_function)(union sigval); /* notify function */
    pthread_attr_t *sigev_notify_attributes;      /* notify attrs */
};

函数readv和writev

#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);

参数:filedes 文件描述符
iov 指向iovec结构数组的一个指针。
iovcnt 数组元素的个数
返回值:若成功则返回已读、写的字节数,若出错则返回-1

struct iovec {
  void *iov_base; /* 起始地址 */
  size_t iov_len; /* 需要传输的字节数 */
};

readv() 系统调用从文件描述符 fd 关联的文件里读取数据到 iovcnt 个由 iov 结果描述的缓存区里。(分散读)
writev() 系统调用把 iovcnt 个由 iov 结构描述的缓存区数据写入文件描述符 fd 关联的文件里。(聚合写)

存储映射I/O

mmap函数

#include <sys/mman.h>
void *mmap (void *addr, size_t len, int prot,  int flag, int fd, __off_t off);  

addr:指定映射区的起始地址,0表示由系统选择,该函数返回该映射区的起始地址
fd:被映射文件的描述符
len:映射的字节数
off:映射字节在文件中的起始偏移量
prot:映射存储区的保护请求,对指定映射区的保护请求不能超过文件open模式访问权限

prot 说明
PORT_READ 可读
PORT_WRITE 可写
PORT_EXEC 可执行
PORT_NONE 不可访问

flag:影响映射存储区的属性
(1)MAP_SHARED:存储操作会修改映射的文件
(2)MAP_PRIVATE:对映射区的存储操作会创建该映射文件的一私有副本,不会影响原文件

mprotect函数

#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
地址参数addr必须是系统页长的整数倍
如果修改的页时通过MAP_SHARED标志映射到地址空间的,那么修改不会立即写会到文件中,何时写会由内核的守护进程决定,如果只修改了一页中的一个字节,整个页都会被写会

可以调用msync将该页冲洗到被映射的文件中

#include <sys/mman.h>
int msync(void *addr,  size_t len, int flags);

进程终止时,会自动解除存储映射区的映射,直接调用munman函数也可以解除映射区,
关闭映射存储区时使用的文件描述符并不解除映射区

#include <sys/mman.h>
int mumap(void *addr, size_t len);
调用munmap并不会使映射区的内容写会到磁盘上

多进程利用存储映射I/O复制文件

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}
int main(int argc, char *argv[])
{
    int N = 5;         //默认为5个
    if (argc < 3 || argc > 4) 
        sys_err("please enter like this: ./a.out file_src file_dst [process_number]");
    if (argc == 4)
        N = atoi(argv[3]);      //获取输入的进程个数

    int fd_src = open(argv[1], O_RDONLY);
    if (fd_src < 0)
        sys_err("open source file error");

    int fd_dst = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd_dst < 0)
        sys_err("open dst file error");

    struct stat sbuf;

    int ret = fstat(fd_src, &sbuf);
    if (ret < 0) 
        sys_err("read file information error");


    int flen = sbuf.st_size;
    if (flen < N)
        N = flen;

    ret = ftruncate(fd_dst, flen);
    if (ret < 0)
        sys_err("ftruncate error");

    char *mp_src = (char *)mmap(NULL, flen, PROT_READ, MAP_SHARED, fd_src, 0);
    if (mp_src == MAP_FAILED)
        perror("mmap error");

    close(fd_src);

    char *mp_dst = (char *)mmap(NULL, flen, PROT_READ | PROT_WRITE, MAP_SHARED, fd_dst, 0);
    if (mp_dst == MAP_FAILED)
        perror("mmap error");

    close(fd_dst);

    int num = flen / N;
    int remainder = flen % num;  //均分后剩下的部分

    pid_t pid;
    int i;
    for(i = 0; i < N; ++i) {
        printf("create %dth proc\n",i);
        pid = fork();
        if (pid == 0)
            break;
    }

    if (i == N) {
        for(int j = 0; j < N; ++j)
            wait(NULL);
    }
    else if (i == N-1) {
        memcpy(mp_dst + i*num, mp_src + i*num, num + remainder);
    }
    else {
        memcpy(mp_dst + i*num, mp_src + i*num, num); 
    }

    munmap(mp_src, flen);
    munmap(mp_dst, flen);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/chuxin126/article/details/77775873