《嵌入式linux應用程序開發標准教程》筆記——6.文件IO編程


  前段時間看APUE,確實比較詳細,不過過於詳細了,當成工具書倒是比較合適,還是讀一讀這種培訓機構的書籍,進度會比較快,遇到問題時再回去翻翻APUE,這樣的效率可能更高一些。

  《嵌入式linux應用程序開發標准教程》的前幾章沒必要看了,都是寫淺顯的知識點,從第六章文件IO編程開始記錄筆記。后期再根據APUE的內容進行補充和擴展。

 

 一、linux系統調用及API

  1. 系統調用

  linux分為內核空間和用戶空間,用戶空間無法直接訪問內核空間。內核通過系統調用為用戶提供服務,很精簡,大約250個左右。大致可分為:進程控制、進程間通信、文件系統控制、系統控制、存儲管理、網絡管理、socket控制、用戶管理等幾類。

  2. C庫API

  C庫提供若干API,遵循一定的標准,供用戶使用。

  用戶可以直接調用系統調用,也可以調用C庫提供的API。

 

 二、linux中文件及文件描述符概述

  linux主要有4中文件:普通文件、目錄文件、鏈接文件、設備文件。

  linux使用文件描述符操作文件,尤其對於用戶態來說更是如此,文件描述符是一個非負的整數,是個索引值,linux打開文件時動態分配,分配時,優先分配未使用的最小描述符。打開文件時,內核返回給進程1個文件描述符,讀寫時進程用此描述符操作文件。 

  進程打開時,默認會打開三個文件描述符,三個文件默認均指向終端:

  STDIN_FILENO: 0,標准輸入

  STDOUT_FILENO: 1,標准輸出

  STDERR_FILENO: 2,標准錯誤

 

 三、底層文件IO操作

  3.1 基本文件操作

     3.1.1 函數說明

  5個基本函數,不帶緩沖,不屬於ANSI C,屬於POSIX標准。

  open、read、write、lseek、close, 見APUE相關筆記。

  3.2 文件鎖

   3.2.1 共享問題

 

  如上圖,進程信息中包含“打開文件的當前文件偏移量”,由進程各自維護。若進程1打開了文件並定位到文件尾部——>切換到進程2,進程2定位到文件尾部並寫了100個字節——>在回到進程1,寫了10個字節——>則結果是該文件的最后100個字節中,前10個是進程1寫的,並覆蓋了原來進程2寫的前10個字節,而后90個是進程2寫的。

  事與願違,故有的場合期望對文件操作時獨占的,所以引入的記錄鎖。

  只要多個進程操作同一個文件,就應該上鎖。

   3.2.2 fcntl()

需要頭文件

#include <sys/types.h>

#include <unistd.h>

#include <fcnt1.h>

#include <sys/select.h>

原型

int fcnt1(int  filedes, int  cmd,.../* struct flock * flockptr * / ) ;

描述

除了記錄鎖以外,還有好多其他的功能

形參

filedes

文件描述符

cmd

記錄鎖相關的

F_GETLK:根據第三個參數的情況測試是否可以上鎖,要上的鎖由flockptr描述.決定由f l o c k p t r所描述的鎖是否被另外一把鎖所排斥(阻塞)。如果存在一把鎖,它阻止創建由 f l o c k p t r所描述的鎖則這把現存的鎖的信息寫到 f l o c k p t r指向的結構中。如果不存在這種情況,則除了將 l _ t y p e設置為F _ U N L C K之外, f l o c k p t r所指向結構中的其他信息保持不變。

  • 若該文件可以上鎖(當前未上鎖),則flockptr->l_type為F_UNLK, flockptr的其他項不變;
  • 若不能上鎖則將該文件已經上鎖的信息通過flockptr返回此時不返回錯誤。
  • 若flockptr->l_type為F_UNLCK(要上的鎖,不能使這個),則返回錯誤

 F_SETLK:根據flockptr設置鎖。如果試圖建立一把按上述兼容性規則並不允許的鎖,則f c n t l立即出錯返回,此時e r r n o設置為E A C          C E SE A G A I N 

 F_SETLKW: F_SETLK的阻塞版.

flockptr

l_type:

  •  F_RDLCK:讀取鎖,共享鎖,加讀鎖時,文件必須包含讀打開
  •  F_WRLCK:寫入鎖,排斥鎖,加寫鎖時,文件必須包含寫打開
  •  F_UNCLK:解鎖

寫入鎖是互斥鎖,讀取鎖是共享鎖。

如果在一個給定字節上已經有一把或多把讀鎖,則不能在該字節上再加寫鎖;如果在一個字節上已經有一把獨占性的寫鎖,則不能再對它加任何讀鎖

 

 l_start和l_whence:其實保護位置(文件偏移)和相對位置(SEEK_CUR/SET/END)。與,lseek相同。

 len想保護的長度,若len=0,則表示從l_start和l_whence決定的位置開始,到文件結尾的最大長度。

若想保護整個文件,則可以l_start = 0,l_whence=SEEK_SET, len=0。

保護區域可以超過尾部,但不能在起始位置之前。

返回值

若成功則依賴於c m d,若出錯則為- 1時

注意事項

用F_GETLK測試,再用F_ SETLK或F_ SETLKW上鎖不是原子操作,可能會有問題!

應該用F_ SETLK直接上,並查詢結果,確定是否上鎖成功。

 【注意】:

    !記錄鎖繼承性等問題

  1. 進程、文件關閉與鎖的關系 由於鎖是在進程信息里存放的,故關閉進程或者關閉文件,該文件的鎖有自動關閉。
  2. 由fork產生的子進程不繼承父進程的鎖。
  3. 執行exec后,新進程可以繼承原來的鎖。默認繼承

     死鎖舉例

 

       如圖,進程1鎖文件1,進程2鎖文件2。若進程1想鎖文件2,且選擇了阻塞方式,則進程1會處於阻塞狀態,同理進程2也是。這樣就構成了死鎖。 

   使用方法:

   使用F_GETLK后再F_SETLK,不是原子操作,可能會出問題。可以直接用F_SETLK,然后判斷返回值。

 

  3.2.3 例程

/* 6-2,fcntl test */

#include <stdio.h>    // printf
#include <stdlib.h>    // exit
#include <unistd.h>
#include <fcntl.h>    // open,fcntl


void lock_set(int fd, short lock_type)
{
    struct     flock st_lock;

    st_lock.l_type = lock_type;
    st_lock.l_whence = SEEK_SET;
    st_lock.l_start = 0;
    st_lock.l_len = 0;
    st_lock.l_pid = -1;
    if( fcntl(fd,F_GETLK,&st_lock) < 0 )        // 判斷st_lock類型的鎖能否上,測試類型,用F_GETLK然后再F_SETLCK,不是原子原子操作,會有問題,例程這么設計,主要是為了方便理解讀寫鎖的互斥性
        printf("\r\nfcntl GETLK err.");
    // 如果能上,則l_type返回F_UNLCK,st_lock的其他域不變
    // 如果不能上,則l_type返回目前的上鎖狀態,同時l_pid返回對該文件上鎖的進程pid
    if( st_lock.l_type !=  F_UNLCK )        
    {
        if( st_lock.l_type == F_RDLCK )
            printf("\r\nRead lock is already locked by pid %d",st_lock.l_pid );
        if( st_lock.l_type == F_WRLCK )
            printf("\r\nWrite lock is already locked by pid %d",st_lock.l_pid );
    }

    printf("\r\nst_lock.l_type:%d",st_lock.l_type);    
    printf("\r\nst_lock.l_whence:%d",st_lock.l_whence);    
    printf("\r\nst_lock.l_start:%d",st_lock.l_start);    
    printf("\r\nst_lock.l_len:%d",st_lock.l_len);    
    printf("\r\nst_lock.l_pid:%d",st_lock.l_pid);    

    
    st_lock.l_type = lock_type;
    if( fcntl(fd,F_SETLKW,&st_lock) < 0 )        
        printf("\r\nfcntl SETLK err.");    
    switch( st_lock.l_type )
    {
    case F_RDLCK:
        printf("\r\nRead lock is set by pid %d",getpid() );
        break;
    case F_WRLCK:
        printf("\r\Write lock is set by pid %d",getpid() );
        break;
    case F_UNLCK:
        printf("\r\nUn lock is set by pid %d",getpid() );
        break;
    defualt:
        break;
    }
}


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

    // open file
    fd=open("hello",O_RDWR|O_CREAT,0644);
    if( fd<0 )    
        printf("\r\nopen file err");
    
    //lock_set(fd,F_RDLCK);        // 讀鎖
    lock_set(fd,F_WRLCK);        // 寫鎖
    getchar();            // 等待終端輸入
    lock_set(fd,F_UNLCK);
    printf("\r\nFINISH\r\n");
    exit(0);
}

 

 

終端窗口運行加讀鎖:
st_lock.l_type:2          // 可上讀鎖,所以F_GETLCK返回F_UNLCK,其他域不變 st_lock.l_whence:0 st_lock.l_start:0 st_lock.l_len:0 st_lock.l_pid:-1 Read lock is set by pid 7377 fcntl GETLK err.          // F_GETLCK+F_UNLCK會返回錯誤 st_lock.l_type:2 st_lock.l_whence:0 st_lock.l_start:0 st_lock.l_len:0 st_lock.l_pid:-1 Un lock is set by pid 7377 FINISH

另一個終端窗口運行加讀鎖:

  st_lock.l_type:2            // 讀鎖是共享鎖,可以隨便加,不受影響
  st_lock.l_whence:0
  st_lock.l_start:0
  st_lock.l_len:0
  st_lock.l_pid:-1
  Read lock is set by pid 7426     

  fcntl GETLK err.
  st_lock.l_type:2
  st_lock.l_whence:0
  st_lock.l_start:0
  st_lock.l_len:0
  st_lock.l_pid:-1
  Un lock is set by pid 7426
  FINISH

// 終端1先運行

st_lock.l_type:2
st_lock.l_whence:0
st_lock.l_start:0
st_lock.l_len:0
Write lock is set by pid 7517

fcntl GETLK err.
st_lock.l_type:2
st_lock.l_whence:0
st_lock.l_start:0
st_lock.l_len:0
st_lock.l_pid:-1
Un lock is set by pid 7517
FINISH

// 終端2后運行

Write lock is already locked by pid 7517   // 寫鎖互斥,釋放后才能加鎖
st_lock.l_type:1
st_lock.l_whence:0
st_lock.l_start:0
st_lock.l_len:0
Write lock is set by pid 7518          // 阻塞加鎖,所以終端1釋放后,馬上加上了

fcntl GETLK err.
st_lock.l_type:2
st_lock.l_whence:0
st_lock.l_start:0
st_lock.l_len:0
st_lock.l_pid:-1
Un lock is set by pid 7518
FINISH

3.3 多路復用

3.3.1 IO模型

  • 阻塞IO,若IO沒有完成相關功能,則進程掛起,直到相關數據到達后才返回。  管道、終端、網絡設備的讀寫經常出現這種情況。
  • 非阻塞IO,IO操作不能完成,理解返回,該進程不睡眠
  • 多路轉換,輪訓各IO,超時等待,select和poll就屬於此類
  • 信號驅動IO,signal

3.3.2 函數說明

 

需要頭文件

#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>

原型

int select(int numfds, fd_set *readfds, fd_set *writefds,fd_set *exeptfds, struct timeval *timeout)

描述

監測多個文件,這就是多路IO的由來

形參

numfds

該參數值為需要監視的文件描述符的最大值加 1

readfds

由 select()監視的讀文件描述符集合

writefds

由 select()監視的寫文件描述符集合

 

exeptfds

由 select()監視的異常處理文件描述符集合

 

timeout

NULL:永遠等待,直到捕捉到信號或文件描述符已准備好為止

具體值:struct timeval 類型的指針,若等待了 timeout 時間還沒有檢
測到任何文件描符准備好,就立即返回

struct timeval
{
long tv_sec; /* 秒 */
long tv_unsec; /* 微秒 */
}

返回值

大於 0:成功,返回准備好的文件描述符的數目
0:超時;-1:出錯

注意事項

監視的文件發生變化,就會返回>0的數

注意:select會改變要監視的文件描述符集,如果需要連續監測,需要注意重新初始化該集合

 

select函數期望對文件描述符分類處理,處理的對象是 描述符的集合,需要使用相關宏定義

FD_ZERO(fd_set *set) 清除一個文件描述符集
FD_SET(int fd, fd_set *set) 將一個文件描述符加入文件描述符集中
FD_CLR(int fd, fd_set *set) 將一個文件描述符從文件描述符集中清除
FD_ISSET(int fd, fd_set*set)如果文件描述符 fd 為 fd_set 集中的一個元素,則返回非零值,可以用於調用 select()之后測試文件描述符集中的文件描述符是否有變化#include <sys/types.h>

#include <poll.h>

int poll( struct pollfd * fds, int numfds, int timerout );
參數:
  fds:描述需要對哪些文件哪種類型操作進行監控。
     struct pollfd
     {
        int fd;      // 需要監聽的文件描述符  
        short events;   // 需要監聽的事件
        short revents;  // 監聽到的事件/已發生的事件
     }
     events可由若干定義好的宏定義描述:

        /* Event types that can be polled for. These bits may be set in `events'
          to indicate the interesting event types; they will appear in `revents'
          to indicate the status of the file descriptor. */
        #define POLLIN 0x001 /* There is data to read. */        文件中有數據可讀
        #define POLLPRI 0x002 /* There is urgent data to read. */    文件中有緊急數據可讀
        #define POLLOUT 0x004 /* Writing now will not block. */     可以向文件中寫入數據

        #ifdef __USE_GNU
        /* These are extensions for Linux. */
        # define POLLMSG 0x400
        # define POLLREMOVE 0x1000
        # define POLLRDHUP 0x2000
        #endif

        /* Event types always implicitly polled for. These bits need not be set in
          `events', but they will appear in `revents' to indicate the status of
          the file descriptor. */
        #define POLLERR 0x008 /* Error condition. */           文件中出現錯誤
        #define POLLHUP 0x010 /* Hung up. */                與文件的鏈接被斷開
        #define POLLNVAL 0x020 /* Invalid polling request. */      文件描述符非法  

  numfds:需要監聽的文件個數,即第一個參數fds的個數
  timeout:超時時間,ms。如果<0,表示無限等待
返回值:
  成功:大於0,表示事件發生的pollfd個數
  0:超時
  -1:出錯

注意事項:
  其實select在系統內部,也是由poll實現的,poll貌似更簡單易用一些。

3.3.3 例程

用兩個終端創建兩個FIFO,第三個終端運行程序,監測兩個FIFO的輸入。

select:

/* 6-3,select */

#include <stdio.h>    // printf
#include <stdlib.h>    // exit
#include <unistd.h>
#include <fcntl.h>    // open,fcntl
#include <sys/time.h>    // struct timeval
#include <sys/select.h>
#include <sys/param.h>    // MAX()

#define BUFF_SIZE    1024

int main(int args, char *argv[])
{
    int fd_max,fd_in1,fd_in2,fd_read;
    fd_set readfds;
    struct timeval timeout;
    char buf[BUFF_SIZE];
    int  read_len;

    // open in1&in2
    if( (fd_in1=open("in1",O_RDWR|O_NONBLOCK)) < 0 )
        printf("\r\n open in1 err");    
    if( (fd_in2=open("in2",O_RDWR|O_NONBLOCK)) < 0 )
        printf("\r\n open in1 err");

    printf("\r\nin1 %d,in2 %d, STDIN_FILENO %d",fd_in1,fd_in2,STDIN_FILENO);
    fflush(stdout);        // 把流flush,printf才能及時打印
    // select 
    FD_ZERO(&readfds);
    FD_SET(fd_in1,&readfds);
    FD_SET(fd_in2,&readfds);
    FD_SET(STDIN_FILENO,&readfds);
    fd_max = MAX(MAX(fd_in1,fd_in2),STDIN_FILENO);
    timeout.tv_sec = 60;
    timeout.tv_usec = 0;
    while( select(fd_max+1, &readfds,NULL,NULL,&timeout) > 0 )
    {
        if( FD_ISSET(fd_in1, &readfds) )
            fd_read = fd_in1;
        if( FD_ISSET(fd_in2, &readfds) )
            fd_read = fd_in2;    
        if( FD_ISSET(STDIN_FILENO, &readfds) )    
            fd_read = STDIN_FILENO;    

        read_len = read(fd_read,buf,BUFF_SIZE);
        if( read_len < 0 )
            printf("\r\nread %d err",fd_read);
        else
        {
            printf("\r\nrcv something from %d",fd_read);
            if( fd_read == STDIN_FILENO )
            {
                if(buf[0] == 'q')
                {
                    printf("\r\nquit by user.");
                    break;
                }
            }
            else
            {
                buf[read_len] = '\0';
                printf( "\r\nRcv data from %d:%s",fd_read,buf )    ;
            }
        }
        // 因為select會改變readfds的值,所以想連續監測,要及時重新設置要監視的fd
        FD_ZERO(&readfds);        
        FD_SET(fd_in1,&readfds);
        FD_SET(fd_in2,&readfds);
        FD_SET(STDIN_FILENO,&readfds);
        fd_max = MAX(MAX(fd_in1,fd_in2),STDIN_FILENO);
    }
        
    printf("\r\n job done");
    exit(0);
}

 

終端1:
mknod in1 p
cat > in1
1111
2222
3333

終端2:
mknod in2 p
cat > in2
4444
5555
6666

終端3運行上述程序,會顯示終端1/2的輸入,'q'則退出

 

poll實現相同的功能,執行效果與select差不多。

/* 6-4,poll */
#include <stdio.h>    // printf
#include <stdlib.h>    // exit
#include <unistd.h>
#include <fcntl.h>    // open,fcntl
#include <sys/time.h>    // struct timeval
#include <sys/select.h>
#include <sys/param.h>    // MAX()
#include <poll.h>

#define BUFF_SIZE    1024

int main(int args, char *argv[])
{
    int fd_in1,fd_in2,fd_read;
    struct pollfd st_pollfd[3];
    char buf[BUFF_SIZE];
    int  read_len;
    char i;

    // open in1&in2
    if( (fd_in1=open("in1",O_RDWR|O_NONBLOCK)) < 0 )
        printf("\r\n open in1 err");    
    if( (fd_in2=open("in2",O_RDWR|O_NONBLOCK)) < 0 )
        printf("\r\n open in1 err");

    printf("\r\nin1 %d,in2 %d, STDIN_FILENO %d",fd_in1,fd_in2,STDIN_FILENO);
    fflush(stdout);        // 把流flush,printf才能及時打印
    // poll 
    st_pollfd[0].fd = fd_in1;
    st_pollfd[0].events = POLLIN;
    st_pollfd[1].fd = fd_in2;
    st_pollfd[1].events = POLLIN;
    st_pollfd[2].fd = STDIN_FILENO;
    st_pollfd[2].events = POLLIN;

    while( poll (&st_pollfd, 3, 60*1000) > 0 )
    {
        if( st_pollfd[0].revents&POLLIN == POLLIN )
            fd_read = fd_in1;
        if( FD_ISSET(fd_in2, &readfds) )
            fd_read = fd_in2;    
        if( FD_ISSET(STDIN_FILENO, &readfds) )    
            fd_read = STDIN_FILENO;    

        for(i=0;i<3;i++)
        {
            if( st_pollfd[i].revents&POLLIN == POLLIN )
            {
                read_len = read(st_pollfd[i].fd,buf,BUFF_SIZE);
                if( read_len < 0 )
                    printf("\r\nread %d err",st_pollfd[i].fd);
                else
                {
                    printf("\r\nrcv something from %d",st_pollfd[i].fd);
                    if( st_pollfd[i].fd == STDIN_FILENO )
                    {
                        if(buf[0] == 'q')
                        {
                            printf("\r\nquit by user.");
                            break;
                        }
                    }
                    else
                    {
                        buf[read_len] = '\0';
                        printf( "\r\nRcv data from %d:%s",st_pollfd[i].fd,buf )    ;
                    }
                }                
            }
                            
        }
    }
        
    printf("\r\n job done");
    exit(0);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM