前段時間看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所指向結構中的其他信息保持不變。
F_SETLK:根據flockptr設置鎖。如果試圖建立一把按上述兼容性規則並不允許的鎖,則f c n t l立即出錯返回,此時e r r n o設置為E A C C E S或E A G A I N F_SETLKW: F_SETLK的阻塞版. |
|
flockptr |
l_type:
寫入鎖是互斥鎖,讀取鎖是共享鎖。 如果在一個給定字節上已經有一把或多把讀鎖,則不能在該字節上再加寫鎖;如果在一個字節上已經有一把獨占性的寫鎖,則不能再對它加任何讀鎖
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直接上,並查詢結果,確定是否上鎖成功。 |
【注意】:
!記錄鎖繼承性等問題
- 進程、文件關閉與鎖的關系 由於鎖是在進程信息里存放的,故關閉進程或者關閉文件,該文件的鎖有自動關閉。
- 由fork產生的子進程不繼承父進程的鎖。
- 執行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> |
|
原型 |
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 |
返回值 |
大於 0:成功,返回准備好的文件描述符的數目 |
|
注意事項 |
監視的文件發生變化,就會返回>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); }