Linux 高級IO

非阻塞I/O

系統調用有兩類,“低速”系統調用和其他,而低速系統調用可能使進程永遠阻塞,例如讀某些終端設備文件,文件中並沒有數據,讀操作可能使調用者永遠阻塞。

非阻塞I/O使我們可以的一些操作不會永遠的阻塞,如果操作繼續執行將會進入阻塞狀態,則調用立即出錯返回。

比如對於一個給定的描述符,有兩種方法指定其爲非阻塞I/O:
1. 調用open函數打開文件時指定O_NONBLOCK標誌
2. 打開文件後對文件描述符調用fcntl修改文件狀態標誌,同上。


阻塞 非阻塞 異步特點

  • 阻塞IO的特點就是在IO執行的兩個階段(準備數據和拷貝數據)都被阻塞了

  • 非阻塞IO的特點是用戶進程需要不點的主動詢問內核數據準備好了沒有

  • IO多路複用通過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select函數就可以返回

  • 異步IO用戶進程發起read操作後可以立刻開始去做其他事情,而內核會等待數據準備完成然後拷貝到用戶內存,完成後給用戶進程發送一個signal告訴它read操作完成


記錄鎖

當一個進程正在讀或者修改文件的某個部分的時候,使用記錄鎖可以防止其他進程修改同意文件區。

#include <fcntl.h>
int fcntl(int fd, int cmd, .../*struct flock flockptr */);

/*struct flock {
    short l_type;    /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
    short l_whence;  /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
    off_t l_start;   /* Starting offset for lock */
    off_t l_len;     /* Number of bytes to lock */
    pid_t l_pid;     /* PID of process blocking our lock (F_GETLK only) */
};*/
  • l_type 鎖的類型:F_RDLCK(共享讀鎖),F_WRLCK(獨佔寫鎖),F_UNLCK(解鎖一個區域)

  • l_whence 設定偏移位置,SEEK_SET, SEEK_CUR, SEEK_END

  • l_start 設定區域起始位置偏移量

  • l_len 設定鎖定區域的字節長度

  • l_pid 持有鎖的進程ID,僅由F_GETLK返回

讀鎖可以共享,說明第一個進程對文件上鎖爲讀鎖時,其他進程也可以對文件上鎖區域獲取讀鎖進行讀取操作。假如進程想在讀鎖的基礎上獲取寫鎖則會被阻塞。而如果第一個進程對文件上鎖爲寫鎖的話,那麼其他進程對文件無論希望獲取什麼鎖都會被阻塞。

寫鎖例子:

#include<stdio.h> 
#include<unistd.h> 
#include<sys/types.h>  
#include<sys/stat.h>  
#include<fcntl.h>
#include<sys/file.h>

int main()                                                          
{                                                                   
    int fd = open("test.txt",O_RDWR|O_CREAT,0644);                  
    if(fd < 0)                                                      
    {                                                               
        printf("open err\n");                                       
        return -1;                                                  
    }                                                               

    struct flock myflock;                                           
    myflock.l_type = F_WRLCK;  //獨佔性寫鎖 F_RDLCK爲共享讀鎖       
    myflock.l_whence = SEEK_SET; //偏移位置爲文件開頭               
    myflock.l_start = 0; //指定鎖的區域開頭                         
    myflock.l_len = 1024; //指定鎖的區域大小                        

    fcntl(fd, F_SETLKW, &myflock);  //鎖文件,鎖不上說明有別人上鎖,就阻塞等待                                                          

    printf("get lock\n");                                           

    myflock.l_type = F_UNLCK; //解鎖                                
    fcntl(fd, F_SETLKW, &myflock);                                  

    close(fd);
    return 0; 
}      

I/O多路轉接

select函數

在所有POSIX兼容的平臺上,select函數使我們可執行I/O多路轉接。

#include <sys/select.h>
int select(int maxfd, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);
  • 第一個參數爲最大文件描述符編號值加一

  • 中間三個參數readfds,writefds,exceptfds是指向描述符集的指針,分別爲我們關心的可讀,可寫,處於異常條件的描述符集合

  • 最後一個參數爲timeval結構體,指定等待描述符準備好的最大時間,若指定NULL則表示阻塞等待,指定0表示不阻塞立即返回,指定非0值爲正常指定時間

select例子

#include<stdio.h>
#include<unistd.h>
#include<sys/select.h>
#include<sys/types.h>
#include<fcntl.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#define MAXSIZE 1024

int main()
{
    int fd_key, fd_mouse;
    char buf[MAXSIZE];
    // 爲了代碼整潔性可讀性未加錯誤判斷,並且打開此文件可能要root權限
    fd_key = open("/dev/input/event1", O_RDONLY);
    fd_mouse = open("/dev/input/mice", O_RDONLY);
    // 第一個參數 最大文件描述符+1
    int maxfd = fd_key > fd_mouse ? fd_key : fd_mouse;
    maxfd++;
    printf("%d\n",maxfd);
    while(1)
    {
        fd_set rset;
        FD_ZERO(&rset); //將rset所有位置0
        FD_SET(fd_key, &rset);
        FD_SET(fd_mouse, &rset);

        struct timeval tv;
        tv.tv_sec = 1; //秒
        tv.tv_usec = 0; //微秒

        int ret = select(maxfd, &rset, NULL, NULL, &tv);
        if(ret < 0)
        {
            if(errno == EINTR) //被信號中斷
                continue;
            return -1;
        }
        else if(ret > 0)
        {
            if(FD_ISSET(fd_key, &rset))
            {
                //判斷是鍵盤操作
                memset(buf, 0, sizeof(buf));
                read(fd_key, buf, sizeof(buf));
                printf("keyboard event\n");
            }
            else if(FD_ISSET(fd_mouse, &rset))
            {
                //判斷是鼠標操作
                memset(buf, 0, sizeof(buf));
                read(fd_mouse, buf, sizeof(buf));
                printf("mouse event\n");
            }
        }
    }

    close(fd_key);
    close(fd_mouse);
    return 0;
}

epoll函數

在linux的網絡編程中,很長的時間都在使用select來做事件觸發。在linux新的內核中,有了一種替換它的機制,就是epoll。

#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll_create

創建一個epoll句柄,size用來告訴內核這個監聽的數目一共多大,但在Linux 2.6.8開始廢棄這個參數,但依然要大於0。

當創建好epoll句柄後,它就是會佔用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。

epoll_ctl

epoll的事件註冊函數

  • 第一個參數爲epoll_create返回值

  • 第二個參數表示動作
    EPOLL_CTL_ADD:註冊新的fd到epfd中;
    EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
    EPOLL_CTL_DEL:從epfd中刪除一個fd;

  • 第三個參數爲需要監聽的fd

  • 第四個參數struct epoll_event告訴內核要監聽什麼事件

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};

event的取值:
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏
epoll_wait

等待事件的產生,參數events用來從內核得到事件的集合,maxevents告之內核這個events有多大,這個 maxevents的值不能大於創建epoll_create()時的size,參數timeout是超時時間。該函數返回需要處理的事件數目,如返回0表示已超時。


epoll對文件描述符的操作有兩種模式:LT和ET。LT模式是默認模式,LT模式與ET模式的區別如下:

  LT模式:當epoll_wait檢測到描述符事件發生並將此事件通知應用程序,應用程序可以不立即處理該事件。
        下次調用epoll_wait時,會再次響應應用程序並通知此事件。

  ET模式:當epoll_wait檢測到描述符事件發生並將此事件通知應用程序,應用程序必須立即處理該事件。
        如果不處理,下次調用epoll_wait時,不會再次響應應用程序並通知此事件。
    (對應epoll_ctl內的struct epoll_event結構體中的__uint32_t enents的(ET)EPOLLET模式,默認是LT模式)

select和epoll的區別

  • select出現早,使用位域來表示描述符集合
    其缺點是描述符數量的限制,在Linux上一般爲1024,大規模文件通過遍歷fdset找到就緒的描述符,效率低。

  • epoll出現晚,使用紅黑樹來保存文件集合,大規模文件描述符效率高


存儲映射I/O

mmap創建映射區見此博文

發佈了31 篇原創文章 · 獲贊 3 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章