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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章