设备和文件IO
設備與文件
??Linux采用文件系統管理硬件設備,所有的設備都看成是特殊的文件,從而將硬件設備的特性及管理細節對用戶隱藏起來,實現設備無關性。
設備管理的特點
①每個設備都對應文件系統中的一個索引節點,都有一個文件名。
②應用程序通常可以通過系統調用open( )打開設備文件,建立起與目標設備的連接。
③對設備的使用類似于對文件的存取。
④設備驅動程序都是系統內核的一部分,它們必須為系統內核或者它們的子系統提供一個標準的接口。
⑤設備驅動程序使用一些標準的內核服務,如內存分配等。
設備工作原理
設備分類
按設備屬主關系:系統設備、用戶設備。
按設備信息交換單位來分:字符設備、塊設備。
按設備共享屬性來分:獨享設備、共享設備。
Linux設備操作
設備或文件操作兩種方式:用戶編程接口 API、系統調用。
系統調用
系統調用是操作系統提供給用戶的一組“特殊”接口。
系統調用并非直接和程序員或系統管理員直接打交道,而是通過軟中斷的方式向內核提交請求,從而獲取內核函數的服務入口(系統調用表)。
系統調用讓系統從用戶空間進入內核空間內運行,運行后將結果返回給應用程序(內核態–>用戶空間)。
系統調用和系統API等區別
系統API
主要是通過C庫libc來實現,程序員多采用這種方式與內核交互,這些API通過系統調用來實現。
系統命令
系統管理員采用系統命令與內核交互,是一個可執行文件,通過系統API及系統調用來實現。
外殼程序
一系列系統命令和SHELL腳本共同組合成的程序。
函數庫調用與系統調用
| 在所有啊ANSIC編譯器版本中,C庫函數是相同的 | 各個操作系統的系統調用是不同的 |
| 它調用函數庫中的一段程序(或函數) | 它調用系統內核的服務 |
| 與用戶程序相聯系 | 是操作系統的一個入口函數 |
| 在用戶地址空間執行 | 在內核地址空間執行 |
| 它的運行時間屬于“用戶時間” | 它的運行時間屬于“系統”時間 |
| 屬于過程調用,調用開銷較小 | 需要在用戶空間和內核上下文環境間切換,開銷較大 |
| 在C函數庫libc中大約300個函數 | 在UNIX中大約有90個系統調用 |
| 典型的C函數庫調用:system fprintf malloc | 典型的系統調用:chdir fork write brk |
C庫的文件操作
| fopen( ) | 打開文件 |
| fclose( ) | 關閉文件 |
| fputc( ) | 將字符寫入文件中 |
| fgetc( ) | 從文件中讀取字符 |
| fread( ) | 將數據從文件中讀到緩沖區 |
| fwrite( ) | 將數據從緩沖區寫入文件 |
| fseek( ) | 在文件中搜索指定位置 |
| fprintf( ) | 操作類似于 printf(),但是用于文件 |
| fscanf( ) | 操作類似于 scanf(),但是用于文件 |
| feof( ) | 如果到達文件結尾,返回 true |
| ferror( ) | 如果出錯,返回 true |
| rewind( ) | 將文件位置指示器重新置于文件開頭 |
| remove( ) | 刪除文件 |
| fflush( ) | 將內部緩沖區的數據寫入指定文件 |
文件描述符fd
每個進程PCB結構中有文件描述符指針,指向files_struct的文件描述符表,記錄每個進程打開的文件列表。
系統內核不允許應用程序訪問進程的文件描述符表,只返回這些結構的索引即文件描述符ID(File Description)給應用程序。
Linux系統中,應用程序通過這些文件描述符來實現讓內核對文件的訪問每個進程能夠訪問的文件描述符是有限制的,通過ulimit –n可以查看。
在Linux系統中有三個已經被分配的文件描述符,分別是:
0 STDIN_FILENO 標準輸入流 1 STDOUT_FILENO 標準輸出流 2 STDERR_FILENO 標準錯誤流這三個文件描述符和它們各自的功能是綁死的,每個進程被加載后,默認打開0,1,2這三個文件描述符。
系統函數
open系統調用
在Linux下,用open函數可以用來打開或創建一個文件:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int open(const char *path, int flags); int open(const char *path, int flags,mode_t mode);參數:path:文件的名稱,可以包含(絕對和相對)路徑flags:文件打開模式mode:用來規定對該文件的所有者,文件的用戶組及系統中其他用戶的訪問權限,則文件權限為:mode&(~umask) 返回值:打開成功,返回文件描述符;打開失敗,返回-1打開文件的方式(flags的值)
O_RDONLY 只讀打開 O_WRONLY 只寫打開 O_RDWR 可讀可寫打開 O_APPEND 表示追加。如果文件已有內容,這次打開文件所寫的數據附加到文件的末尾而不覆蓋原來的內容。 O_CREAT 若此文件不存在則創建它。使用此選項時需要提供第三個參數mode,表示該文件的訪問權限。 O_EXCL 如果同時指定了O_CREAT,并且文件已存在,則出錯返回。 O_TRUNC 如果文件已存在,并且以只寫或可讀可寫方式打開,則將其長度截斷(Truncate)為0字節。 O_NONBLOCK 對于設備文件,以O_NONBLOCK方式打開可以做非阻塞I/O(Nonblock I/O),非阻塞I/O在下一節詳細講解。訪問權限(mode的值)
S_IRUSR 文件所有者的讀權限位 S_IWUSR 文件所有者的寫權限位 S_IXUSR 文件所有者的執行權限位 S_IRWXU S_IRUSR|S_IWUSR|S_IXUSR S_IRGRP 文件用戶組的讀權限位 S_IWGRP 文件用戶組的寫權限位 S_IXGRP 文件用戶組的執行權限位 S_IRWXG S_IRGRP|S_IWGRP|S_IXGRP S_IROTH 文件其他用戶的讀權限位 S_IWOTH 文件其他用戶的寫權限位 S_IXOTH 文件其他用戶的執行權限位 S_IRWXO S_IROTH|S_IWOTH|S_IXOTHopen函數與C標準I/O庫的fopen函數的區別:
①以可寫的方式fopen一個文件時,如果文件不存在會自動創建,而open一個文件時必須明確指定O_CREAT才會創建文件,否則文件不存在就出錯返回。
②以w或w+方式fopen一個文件時,如果文件已存在就截斷為0字節,而open一個文件時必須明確指定O_TRUNC才會截斷文件,否則直接在原來的數據上改寫。
③第三個參數mode指定文件權限,可以用八進制數表示,比如0644表示-rw-r-r–,也可以用S_IRUSR、S_IWUSR等宏定義按位或起來表示。
示例:
close系統調用
close函數關閉一個已打開的文件:
#include <unistd.h>int close(int fd);參數:fd :要關閉的文件的文件描述符 返回值:成功返回0,出錯返回-1并設置errnoread系統調用
read函數從打開的設備或文件中讀取數據
#include <unistd.h>int read(int fd, void *buf, size_t nbytes);參數:fd :想要讀的文件的文件描述符buf: 指向內存塊的指針,從文件中讀取來的字節放到這個內存塊中nbytes: 從該文件復制到buf中的字節個數 返回值:成功返回讀取的字節數,出錯返回-1并設置errno,如果在調read之前已到達文件末尾,則這次read返回0write系統調用
write函數向打開的設備或文件中寫數據
#include <unistd.h>int write(int fd, const void *buf, size_t nbytes);參數:fd :要寫入的文件的文件描述符buf: 指向內存塊的指針,從這個內存塊中讀取數據寫入到文件中nbytes: 要寫入文件的字節個數 返回值:成功返回寫入的字節數,出錯返回-1并設置errno示例:
#include <stdio.h> #include <fcntl.h> #include <sys/stat.h> void main()int outfd = 0, r_size = 0;char buf[ ] = "Hello world!!";fd = open("test",O_WONLY | O_TRUNC | O_CREAT,S_IRWXU);if(outfd>0){r_size = write(outfd,buf,sizeof(buf));if(r_size>0){printf("write data to file success!");} close(fd); } }lseek系統調用
lseek和標準I/O庫的fseek函數類似,可以移動當前讀寫位置(或者叫偏移量)。
#include <sys/types.h> #include <unistd.h>off_t lseek(int fd, off_t offset, int base); //這里允許偏移超過文件末尾,中間空出來的位置都是0.參數:fd:需設置的文件標識符offset:偏移量base:搜索的起始位置 返回值:返回新的文件偏移值base 表示搜索的起始位置,有以下幾個值:
SEEK_SET,從文件開始處計算偏移 SEEK_CUR,offset為相對當前位置的位置 SEEK_END,offset為相對文件結尾的位置若lseek成功執行,則返回新的偏移量,因此可用以下方法確定一個打開文件的當前偏移量。如:
off_t currpos; currpos = lseek(fd, 0, SEEK_CUR);fcntl系統調用
這里先引進文件記錄鎖的概念:當有多個進程同時對某一文件進行操作時,就有可能發生數據的不同步,從而引起錯誤,該文件的最后狀態取決于寫該文件的最后一個程序。
Linux中文件記錄鎖可以對文件某一區域進行文件記錄鎖的控制。它是通過fcntl函數來實現的,fcntl是管理文件記錄鎖的操作。
#include <unistd.h> #include <fcntl.h>int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); int fcntl(int fd, int cmd, struct flock *lock);參數:fd:文件描述符;cmd:功能符號;(F_SETLK用來設置或釋放鎖;F_GETLK用來獲得鎖信息;)lock:存儲鎖信息的結構體指針; 返回值:調用成功返回0,失敗返回-1鎖信息結構體
struct flock {short l_type; /* 鎖的類型 */short l_whence; /* 偏移量的起始位置: */off_t l_start; /* 從l_whence的偏移量 */off_t l_len; /* 從l_start開始的字節數 */ pid_t l_pid; /* 鎖所屬進程ID(一般不用) */ } l_type有F_RDLCK讀鎖、F_WRLCK寫鎖及F_UNLCK空鎖。 l_whence有SEEK_SET、SEEK_CUR和SEEK_END。 l_len為0時表示從起點開始直至最大可能位置為止。示例:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main() {int fd;struct flock lock;if((fd = open("test",O_CREAT | O_TRUNC | O_RDWR, S_IRWXU)) == -1){printf("open file error\n");return -1;}memset(&lock,0,sizeof(struct flock));lock.l_start = SEEK_SET;lock.l_whence = 0;lock.l_len = 0;if(fcntl(fd,F_GETLK,&lock) == 0){if(lock.l_type != F_UNLCK){printf("lock can not by set in fd\n");}else{lock.l_type = F_WRLCK;if(fcntl(fd,F_SETLK,&lock) == 0)printf("set write lock success!\n");elseprintf("set write lock fail!\n");getchar();lock.l_type = F_UNLCK;fcntl(fd,F_SETLK,&lock);}}close(fd);return 0; }總結
- 上一篇: SQLite3单例模式(C++)
- 下一篇: 学习笔记——os模块常见列表