Linux系統(tǒng)下I/O
一、I/O簡介
I/O(輸入/輸出)是在主存和外部設備(磁盤驅(qū)動器、網(wǎng)絡、終端)之間復制數(shù)據(jù)的過程。輸入是從外部設備復制到主存,輸出是從主存復制到外部設備。
在Linux系統(tǒng)中所有的I/O設備都被映射稱為文件,所有的輸入輸出都被當做相應文件的讀和寫來執(zhí)行,所以內(nèi)核提供了系統(tǒng)級的I/O函數(shù)接口,使得所有輸入輸出都以統(tǒng)一且一致的方式來執(zhí)行。
- 打開文件,返回一個非負整數(shù),叫做描述符
- 每個進程都默認打開三個描述符,標準輸入 STDIN_FILENO(描述符0)、標準輸出 STDOUT_FILENO(描述符1)、標準出錯 STDERR_FILENO(描述符2)。
- 讀寫文件,讀就是從文件復制n個字節(jié)到內(nèi)存,寫就是從內(nèi)存復制n個字節(jié)到文件。
- 文件偏移:默認打開文件是從文件開頭起始的字節(jié)偏移量,可以使用seek來操作。
- 關閉文件。
今天從四個方面來說I/O,文件I/O、標準I/O庫、高級I/O、終端I/O。
- 文件I/O: 文件的打卡、讀寫、關閉、偏移。
- 標準I/O庫:Linux提供的標準I/O庫函數(shù)
- 高級I/O:非阻塞I/O、I/O多路轉(zhuǎn)接、異步I/O
- 終端I/O: 更改終端屬性操作的函數(shù)
二、文件I/O
Linux系統(tǒng)中文件I/O一般只用到以下五個函數(shù):open、read、write、lseek、close。每次read、write都是一次系統(tǒng)調(diào)用(從用戶層拷貝到內(nèi)核層再拷貝到用戶層)且不帶緩沖。
- 文件描述符
對于內(nèi)核而言,每個打開的文件都是通過文件描述符引用的,每個文件描述符都是一個非負整數(shù),打開或者創(chuàng)建一個文件都會返回一個文件描述符,通過這個文件描述符來進行讀寫,
- 打開/創(chuàng)建文件
#include
#include
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:創(chuàng)建或者打開一個文件,返回一個文件描述符
參數(shù): pathname:路徑名/文件名
flags:標志位
O_RDONLY 只讀
O_WRONLY 只寫
O_RDWR 既可以讀也可以寫
O_APPEND 以追加的方式操作文件
O_CREAT 如果文件不存在,則創(chuàng)建
O_TRUNC 如果文件存在,則清空文件的數(shù)據(jù)
O_EXCL 表示文件已經(jīng)存在,而又重復創(chuàng)建一次,open函數(shù)會返回錯誤,
返回文件已經(jīng)存在的錯誤,對錯誤做處理之后,直接打開文件就可以
O_APPEND 從文件末尾位置追加寫入
O_SYNC 每次write等物理I/O操作完成,包括由該write操作引起的文件屬性更新所需的I/O,(后邊會用到)
O_RSYNC 每個以文件描述符作為參數(shù)進行的read操作等待,直到所有對文件同一部分掛起的寫操作都完成。
mode:如果是創(chuàng)建一個文件,需要添加對應文件的屬性,模式屬性一般用一個八進制數(shù)代替,如果屬性成立,為1,不成立,則為0
rwxr-x-wx --> 0753
rw-rw-r-- --> 0664
返回值:成功:文件描述符
失敗:-1
- 關閉文件
關閉一個文件時會自動釋放加在該文件上的所有鎖,當進程終止時會自動關閉所有打開的文件。
int close(int fd);
參數(shù):fd:open返回的文件描述符
返回值:成功 0, 失敗 -1.
- 文件偏移
通常所有讀寫操作都是從當前文件偏移量處開始,并使偏移量增加讀寫的字節(jié)數(shù),默認是0。可以使用lseek顯式打開文件設置偏移量。
#include
off_t lseek(int fd, off_t offset, int whence);
參數(shù):fd : open函數(shù)打開的文件
offset: 與whence有關
whence: 基準點
SEEK_SET 將讀寫位置指向文件頭后再增加offset個位移量。
SEEK_CUR 以目前的讀寫位置往后增加offset個位移量。
SEEK_END 將讀寫位置指向文件尾后再增加offset個位移量(使用該參數(shù)可以算出文件字節(jié)數(shù))
當whence 值為SEEK_CUR 或SEEK_END時,參數(shù)offet允許負值的出現(xiàn)。
返回值:成功,返回文件偏移量,失敗 -1.
注釋:文件偏移量可以大于文件長度,這樣就會構成空洞文件,對于多出的這些字節(jié)被讀出為0.空洞文件在磁盤中不占用存儲區(qū)。
- 讀文件
ssize_t read(int fd, void *buf, size_t count);
參數(shù):
fd:文件描述符
buf:讀取到的數(shù)據(jù)
const:每一次最多讀取到的字節(jié)數(shù)
返回值:
成功:讀取的字節(jié)數(shù) 如果是0 代表結尾
失敗:-1
- 寫文件
ssize_t write(int fd, const void *buf, size_t count);
功能:向一個文件描述符寫數(shù)據(jù)
參數(shù):
fd:文件描述符
buf:要寫入的數(shù)據(jù)
const:每一次最多寫入到的字節(jié)數(shù)
返回值:
成功:寫入的字節(jié)個數(shù)
失敗:-1
失敗原因多是磁盤已滿或者超過一個給定進程的文件長度限制。
- 文件共享
Linux系統(tǒng)支持不同進程間共享打開文件,在此先說一下內(nèi)核用于所以I/O的數(shù)據(jù)結構。
內(nèi)核使用三種數(shù)據(jù)結構表示打開的文件,他們之間的關系決定了文件共享中一個進程對另一個進程的影響。 首先每個進程在進程表中有一個記錄項,每個記錄項包含一張打開的文件描述符,每個描述符占用一項,與文件描述符有關的是:
1.文件描述符標志
2.指向文件表項的指針
其次內(nèi)核為每個打開文件維持一張文件表,文件表項包含:
1.文件狀態(tài)標志(讀、寫、阻塞等)
2.當前文件偏移量
3.指向該文件v節(jié)點表項指針 最后每個打開文件(設備)都有一個v節(jié)點結構,它包含了:
1.文件類型
2.對該文件進行各種操作的指針。
3.i節(jié)點(i-node),包含了文件的長度、所以者、指向文件實際數(shù)據(jù)塊在磁盤的位置。
這些信息都是在打開文件時候從磁盤拷貝到內(nèi)存,所以這些信息都是隨時可用的。總結一下這三張表關系
文件指針(文件表項):文件狀態(tài)標志
當前文件偏移量
v節(jié)點指針(v節(jié)點表項): v節(jié)點信息
v_data: i節(jié)點(i節(jié)點表項): i節(jié)點信息
當前文件長度
了解了內(nèi)核的這三個數(shù)據(jù)結構之后我們回過頭來看文件共享。
假定一個進程打開了一個文件,返回文件描述符是4,另一個進程也打開了這個文件描述符返回的文件描述符是5,打開該文件的每個進程都有一個文件表項(進程對該文件的當前偏移量),但是該文件只有一個v節(jié)點。
- 每當write之后,文件表項中擔負起偏移量會增加寫入的字節(jié)數(shù),如果當前文件偏移量超出了當前文件長度則i節(jié)點表項中文件長度也增加。
- 如果使用O_APPEND打開一個文件,相應的標志被設置到文件表項中的文件狀態(tài)標志,每次對該文件寫操作時,文件表項中當前文件偏移量會被設置為i節(jié)點表項的文件長度。
- 當使用lseek函數(shù)定位到文件尾端時候,文件表項中的當前文件偏移量被設置為i節(jié)點表項中的文件長度。
- 存在多個文件描述符指向同一個文件的情況。
需要C/C++ Linux服務器架構師學習資料加qun579733396獲取(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協(xié)程,DPDK,ffmpeg等),免費分享
- 原子操作
當有多個進程操作一個文件時候為了數(shù)據(jù)同步Linux系統(tǒng)提供了原子操作。
1.open一個文件時候使用 O_APPEND 標志
2.使用pread 和 pwrite 函數(shù) pread/pwrite 相當于調(diào)用lseek之后調(diào)用read/write,但是區(qū)別在于調(diào)用pread/pwrite時,無法中斷其定位和讀寫操作,而且不更新當前文件的偏移量。
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
功能:讀文件
參數(shù):fd:文件描述符
buf:讀緩沖區(qū)
count:緩沖區(qū)大小
offset:偏移量
返回值: 成功:讀到的字節(jié)數(shù),如果讀到文件尾返回0, 失敗-1
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
功能:寫文件
參數(shù):fd:文件描述符
buf:寫緩沖區(qū)
count:緩沖區(qū)大小
offset:偏移量
返回值: 成功:讀到的字節(jié)數(shù), 失敗-1
- 將緩沖區(qū)數(shù)據(jù)寫到磁盤
在傳統(tǒng)Unix系統(tǒng)實現(xiàn)中大多數(shù)磁盤I/O通過緩沖區(qū)進行的,當我們向文件寫數(shù)據(jù)時,內(nèi)核通常將數(shù)據(jù)復制到緩沖區(qū)中,之后再寫到磁盤,這種方式稱為延遲寫。下面函數(shù)將緩沖區(qū)數(shù)據(jù)寫入到磁盤。
void sync(void);
將修改過的塊緩沖區(qū)排隊寫到隊列就返回,數(shù)據(jù)并不一定寫入到磁盤。命令sync就是調(diào)用sync函數(shù)。update系統(tǒng)守護進程每30s調(diào)用一次該函數(shù)。
int fsync(int fd);
只對一個文件描述符其作用,并且磁盤操作結束后才返回。
int fdatasync(int fd);
等同于fsync,但是同時更新文件屬性。
- 修改已打開的文件屬性
#include
int fcntl(int fd, int cmd, ... /* arg */ );
功能:修改已打開文件屬性
參數(shù):fd:文件描述符
cmd: F_DUPFD:復制文件描述符,新的文件描述符作為返回值返回。新文件描述符與舊fd共享同一文件表項,但是有自己的文件描述符標志,其FD_CLOEXXEC文件描述符標志被取消
F_DUPFD_CLOEXEC:復制文件描述符,設置與新文件描述符關聯(lián)的FD_CLOEXXEC文件描述符標志的值,返回新文件描述符
F_GETFD:對應于fd的文件描述符標志作為函數(shù)返回值
F_SETFD:對應fd設置文件描述符標志,新值為第三參數(shù)值
F_GETFL:對應fd的文件狀態(tài)標志作為函數(shù)返回值
F_SETFL:將文件狀態(tài)標志設置為第三個參數(shù)的值
F_GETOWN:獲取當前SIGIO和SIGURG信號的進程ID和組ID
F_SETOWN:設置接收SIGIO和SIGURG信號的進程ID和組ID
第三參數(shù):總是一個整數(shù),一般0
返回值:出錯:-1
成功:其他
- ioctl 函數(shù)
ioctl函數(shù)是I/O操作的萬金油,內(nèi)核對設備的IO通道控制操作函數(shù),多用于驅(qū)動程序。
int ioctl(int fd, int request, ...);
參數(shù):@fd :文件描述符的序號
@request :請求 代表不同操作的數(shù)字值
@... :可變參數(shù),(寫或者不寫根據(jù)請求決定)
:傳遞的是整數(shù),或者地址
返回值:出錯:-1
成功:其他
ioctl函數(shù)的實現(xiàn)需要一種命令碼
32位
比特位 含義
31 - 30 00 : 命令不帶參數(shù)
01 : 命令從驅(qū)動中獲取數(shù)據(jù),讀方向
10 : 命令把數(shù)據(jù)寫入驅(qū)動,寫方向
11 : 命令即寫又讀:雙向
29 - 16 類型的大小
15 - 8 類型
7 - 0 序號
三 、標準I/O庫
標志I/O庫處理了很多細節(jié),比如緩沖區(qū)的分配、優(yōu)化塊長度執(zhí)行I/O等,更方便大家進行I/O操作
- 流
在前面說的I/O函數(shù)都是圍繞著文件描述符進行操作的,在標準I/O庫里對應的是 流 進行操作的,當打開一個一個流時,標準I/O庫函數(shù)fopen返回一個指向FILE對象的指針。它是一個結構體包含了標準I/O庫
所管理該流的所有信息,包括用于實際I/O的文件描述符、指向用于該流的緩沖區(qū)指針、緩沖區(qū)長度、以及當前緩沖區(qū)中的字符等。
對應文件描述符每個進程定義了三個流,標準輸入(stdin)、標準輸出(stdout)、標準出錯(stderr)
2.緩沖區(qū)
標準I/O庫提供緩沖區(qū)的目的是為了盡可能減少使用read和write(太消耗資源了),它對每個I/O流自動地進行緩沖管理,庫函數(shù)提供的接口,在內(nèi)存中創(chuàng)建一塊緩沖區(qū),直到滿足一定條件,才會真正寫入,本質(zhì)上還是系統(tǒng)調(diào)用,可以在不同系統(tǒng)間進行數(shù)據(jù)傳輸。
有以下三種緩沖
1.全緩沖,操作的文件,3個條件:
- 緩沖區(qū)滿,則會刷新緩沖區(qū) 4096byte
- 程序正常結束
- fflush刷新緩沖區(qū)(將內(nèi)容寫到磁盤,在驅(qū)動程序表示丟棄緩沖區(qū)數(shù)據(jù))
2.行緩沖:指針對終端進行操作,4個條件:
- 緩沖區(qū)滿,則會刷新緩沖區(qū) 1024byte
- 程序正常結束
- fflush刷新緩沖區(qū)
- “n”
3.無緩沖:指針終端進行操作
修改系統(tǒng)默認緩沖(一定要在流打開之后修改)
void setbuf(FILE *stream, char *buf);
功能:打開或者關閉緩沖機制
參數(shù):stream:打開的流
buf:指向一個長度為BUFSIZE的緩沖區(qū),設置為null則關閉緩沖
返回值:成功0,失敗非0
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
功能:打開或者關閉緩沖機制
參數(shù):stream:打開的流
buf:指向一個長度為BUFSIZE的緩沖區(qū),設置為null則系統(tǒng)自動分配
mode:_IONBF :無緩沖,此選項可以忽略buf和size
_IOLBF :行緩沖
_IOFBF :全緩沖
返回值:成功0,失敗非0
刷新緩沖區(qū),將所有未寫的數(shù)據(jù)傳輸?shù)絻?nèi)核。如果stream為null,則刷新所有緩沖區(qū)。
#include
int fflush(FILE *stream);
- 打開流
打開一個流默認是全緩沖,當打開終端設備時候默認為行緩沖。
FILE *fopen(const char *path, const char *mode);
功能:打開一個標準I/O流
參數(shù):path:文件名
mode:打開模式 (b:二進制文件)
r/rb:打開文件對文件進行讀操作,文件必須存在,
r+/r+b/rb+:打開文件對文件進行讀寫操作,文件必須存在
w/wb:打開或者創(chuàng)建文件,對文件進行寫入
w+/w+b/wb+:打開或者創(chuàng)建文件,對文件進行讀寫操作
a/ab:打開或者創(chuàng)建文件,從文件末尾位置追加數(shù)據(jù)(多個進程追加一個文件也可以正確寫入)
a+/a+b/ab+:打開或者創(chuàng)建文件,從文件末尾進行讀取、追加文件。如果文件不存在創(chuàng)建文件,從文件起始處讀寫。
返回值:成功返回文件指針,失敗返回null
FILE *fdopen(int fd, const char *mode);
功能:取一個文件描述符,并使標準I/O流與之相關聯(lián),此函數(shù)常用于由創(chuàng)建管道和網(wǎng)絡通信管道函數(shù)返回的描述符。因為這些特色文件不能用fopen打開。
返回值:成功返回文件指針,失敗返回null
FILE *freopen(const char *path, const char *mode, FILE *stream);
功能:在一個指定流上打開一個文件,如果已經(jīng)打開則先關閉再打開,此函數(shù)一般用于將一個文件打開為一個預定義的流:stdin、stdout、stderr
參數(shù):path:文件名
mode:
返回值:成功返回文件指針,失敗返回null
- 關閉流
當關閉一個流時候,緩沖區(qū)所有數(shù)據(jù)都被丟棄。
int fclose(FILE *fp);
返回值:成功0,失敗EOF(-1)
- 讀流和寫流
每次打開一個I/O可以使用三種不同方式進程讀寫流 1. 每次讀寫一個字符的I/O 2. 每次讀寫一行的I/O,沒次以換行符終止 3. 直接I/O,直接讀寫某種指定長度的對象,常用于二進制和結構體讀寫。
讀寫一個字符
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
功能:讀取數(shù)據(jù)
參數(shù):流
返回值: 成功 讀取的字符,失敗 -1(EOF)
區(qū)別:getc為宏。fgetc為函數(shù),所以fgetc可以當做地址作為參數(shù)傳遞,getc不可以。
#include
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
功能:寫入文件數(shù)據(jù)
參數(shù):c 寫入的字符 stream 流
返回值:成功 寫入的字符,失敗 EOF
- 讀寫一行字符
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);(不推薦使用,因為無法指定長度,可以造成緩沖區(qū)溢出)
功能:讀取文件中的一行字符,遇到n 結束
參數(shù):s 指向用戶開辟的緩沖區(qū),實現(xiàn)定義一個數(shù)組
size:要求讀取字節(jié)個數(shù)
stream:流
返回值:成功 讀取的字符串,失敗 EOF;
#include
int fputs(const char *s, FILE *stream);
int puts(const char *s);
功能:輸出以null結尾的字符串數(shù)據(jù)數(shù)據(jù)到指定文件中
參數(shù):s 指定要被讀取數(shù)據(jù)的緩沖區(qū)
輸出 n 但不能輸出?
返回值:成功 讀取的字符串,失敗 EOF;
- 二進制I/O讀寫
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:讀文件
參數(shù):ptr:事先定義的變量,需要傳遞變量的
size:每個對象的大小
number:對象個數(shù)
stream:流
返回值:成功:返回實際讀取到對象的個數(shù)
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);
功能:寫文件
參數(shù):ptr:事先定義的變量,需要傳遞變量的
size:每個對象的大小
number:對象個數(shù)
stream:流
返回值:成功:返回實際寫對象的個數(shù)
注釋:這兩個函數(shù)存在一個問題就是只能讀寫同一系統(tǒng)上的數(shù)據(jù),如果是不同系統(tǒng)則會造成問題。因為在不同系統(tǒng)同一結構的同一成員偏移量可能不同。
- 定位流
int fseek(FILE *stream, long offset, int whence);
功能:文件定位
參數(shù):stream 流
offset:偏移量
whence:基準點
SEEK_SET 文件開頭位置
SEEK_CUR 文件當前位置
SEEK_END 文件末尾位置
從后往前偏移加 - 號
返回值:成功 0 失敗 -1
long ftell(FILE *stream);
功能 返回當前文件位置指針的位置是在那個地址,使用數(shù)字的形式表示
參數(shù):stream 流
返回值:成功返回文件當前位置,出錯-1.
void rewind(FILE *stream);
參數(shù):stream 流
功能: 把文件指針指向開頭
int fgetpos(FILE *stream, fpos_t *pos);
功能:將文件位置指示器的當前值存入pos指向的對象中
int fsetpos(FILE *stream, fpos_t *pos);
功能:將文件位置定位到pos指示的值位置。
- 格式化I/O
#include
int printf(const char *format, ...);
功能:發(fā)送格式化輸出到標準輸出 stdout。
參數(shù):format -- 這是字符串,包含了要被寫入到標準輸出 stdout 的文本
int fprintf(FILE *stream, const char *format, ...);
功能:寫入到指定的流。
int sprintf(char *str, const char *format, ...);
功能:將格式化字符串寫入到str中,自動會加一個null字節(jié)
參數(shù):str:保存格式化的字符串
format -- 這是字符串
返回值:成功:返回寫入到str中字符數(shù)(不包含null),失敗負數(shù)
int snprintf(char *str, size_t size, const char *format, ...);
功能:同sprintf,但是sprintf可能會造成緩沖區(qū)溢出功能,所以snprintf會限定寫入字節(jié)數(shù)。
參數(shù):str:保存格式化的字符串,自動會加一個null字節(jié)
size:字符串大小
format -- 這是字符串
返回值:如果格式化后的字符串長度小于等于 size,則會把字符串全部復制到 str 中,并給其后添加一個字符串結束符 ?;
如果格式化后的字符串長度大于 size,超過 size 的部分會被截斷,只將其中的 (size-1) 個字符復制到 str 中,并給其后添加一個字符串結束符 ?,返回值為欲寫入的字符串長度。
失敗:負數(shù)
格式字符: %h:輸出short型
%d 十進制有符號整數(shù)
%md:m為指定的輸出字段的寬度。如果數(shù)據(jù)的位數(shù)小于m,則左端補以空格,若大于m,則按實際位數(shù)輸出。
%ld:輸出長整型數(shù)據(jù)。
%lld: long long型
%u 十進制無符號整數(shù)
%f 浮點數(shù) 輸出float
%lf 浮點數(shù) 輸出double
%m.nf:輸出共占m列,其中有n位小數(shù),如數(shù)值寬度小于m左端補空格。
%-m.nf:輸出共占m列,其中有n位小數(shù),如數(shù)值寬度小于m右端補空格。
%s 字符串
%c 單個字符
%p 指針的值
%% 百分號本身
%e 指數(shù)形式的浮點數(shù)
%x, %X 無符號以十六進制表示的整數(shù)
%o 無符號以八進制表示的整數(shù)
%g(%G) 浮點數(shù)不顯無意義的零"0"
%p 輸出地址符
%lu 32位無符號整數(shù)
%llu 64位無符號整數(shù)
附加格式說明符
m 輸出數(shù)據(jù)域?qū)?數(shù)據(jù)長度 .n 對實數(shù),指定小數(shù)點后位數(shù)(四舍五入)
- 輸出數(shù)據(jù)在域內(nèi)左對齊(缺省右對齊)
+ 指定在有符號數(shù)的正數(shù)前顯示正號(+)
0 輸出數(shù)值時指定左面不使用的空位置自動填0
# 在八進制和十六進制數(shù)前顯示前導0,0x
l long類型輸出 %ld
double類型輸出 %lf
格式化輸入:
#include
int scanf(const char *format, ...);
功能:按照格式從終端輸入數(shù)據(jù)
參數(shù):
format:格式控制串
%d 十進制整數(shù)
%c 字符數(shù)據(jù)
%s 字符串
%f 浮點類型
arg:可變參
如果要將輸入的數(shù)據(jù)保存在arg變量里面,需要傳arg的地址
返回值:成功:輸入的個數(shù) 失敗EOF
int fscanf(FILE *stream, const char *format, ...);
功能:從流 stream 讀取格式化輸入
參數(shù):stream :這是指向 FILE 對象的指針,該 FILE 對象標識了流。
format :這是 C 字符串,包含了以下各項中的一個或多個:空格字符、非空格字符 和 format 說明符。
返回值:如果成功,該函數(shù)返回成功匹配和賦值的個數(shù)。如果到達文件末尾或發(fā)生讀錯誤,則返回 EOF。
int sscanf(const char *str, const char *format, ...);
功能:從字符串讀取格式化輸入。
參數(shù):str:這是 C 字符串,是函數(shù)檢索數(shù)據(jù)的源。
format :這是 C 字符串,包含了以下各項中的一個或多個:空格字符、非空格字符 和 format 說明符
返回值:如果成功,該函數(shù)返回成功匹配和賦值的個數(shù)。如果到達文件末尾或發(fā)生讀錯誤,則返回 EOF。
格式字符:同格式化輸出,左補空格;否則按實際輸出
- 臨時文件
char *tmpnam(char *s);
功能:產(chǎn)生一個與現(xiàn)有文件不同名的文件,每次調(diào)用都會產(chǎn)生不同路徑的臨時文件
參數(shù):保存返回的路徑名
返回值:返回文件路徑名
#include
FILE *tmpfile(void);
功能:產(chǎn)生一個臨時二進制文件(wb+),關閉該文件時會自動刪除該文件
參數(shù):保存返回的路徑名
返回值:返回文件路徑名
11 內(nèi)存流
標準I/O庫都是是將文件中數(shù)據(jù)取出來緩沖在內(nèi)存中,現(xiàn)在我們可以直接通過緩沖區(qū)與主存直接來回傳遞數(shù)據(jù),不依賴文件。仍然使用FILE指針,這些流看起來像文件流,其實是內(nèi)存流。
內(nèi)存流不訪問文件只訪問主存,所以如果標準I/O流作為參數(shù)用于臨時文件的話,用內(nèi)存流替代會有很大性能提高。
FILE *fmemopen(void *buf, size_t size, const char *mode);
功能:內(nèi)存流創(chuàng)建
參數(shù):buf:指向緩沖區(qū)的開始位置,如果為null,讀寫都沒有任何意義。
size:指定緩沖區(qū)大小的字節(jié)數(shù),如果buf為null,則自動分配大小
mode:同fopen的mode
返回值:成功 返回流指針,失敗null
FILE *open_memstream(char **ptr, size_t *sizeloc);
功能:創(chuàng)建流面向字節(jié)
#include
FILE *open_wmemstream(wchar_t **ptr, size_t *sizeloc);
功能:創(chuàng)建流面向?qū)捵止?jié)
四、高級I/O
非阻塞I/O、I/O多路轉(zhuǎn)接、異步I/O、記錄鎖,這些都會在進程間通信用到
- 非阻塞I/O
對于給定的文件描述符,有兩種方法指定為非阻塞I/O。
- 調(diào)用open獲得描述符時候指定 O_NONBLOCK標志
- 對于打開的文件描述符,調(diào)用fcntl函數(shù),將O_NONBLOCK標志打開
- 記錄鎖
記錄鎖:當一個進程正在讀或者寫一個文件某部分的時候,使用記錄鎖可以阻止其他進程修改同一文件區(qū)。
對于記錄鎖,cmd的參數(shù)為 F_GETKL、F_SETLK、F_SETLKW。第三個參數(shù)為指向flock結構的指針
struct flock {
short l_type; 鎖的類型:F_RDLCK(共享讀鎖)、F_WRLCK(獨占性寫鎖)、F_UNLCK(解鎖)
short l_whence; SEEK_CUR、SEEK_SET、SEEK_END
off_t l_start; 加鎖或者解鎖的區(qū)域起始偏移量
off_t l_len; 區(qū)域長度
pid_t l_pid; 持有鎖阻塞當前的進程
};
如果len為0,表示鎖的范圍無限大,不管向文件追加多少數(shù)據(jù)都在鎖范圍內(nèi)。
對整個文件加鎖,len=0,whence=EEK_SET。
共享讀鎖:任意多個進程可以在給定字節(jié)上有一把共享讀鎖,
獨占性寫鎖:如果給定字節(jié)已經(jīng)有寫鎖,那么不可再加任何鎖。
F_GETKL:判斷由flock結構的指針所描述的鎖是否會被另外一把鎖排斥。如果存在一把鎖,它阻止創(chuàng)建由flock結構的指針所描述的鎖,如果不存在則吧type修改為F_UNLCK
F_SETLK:由flock結構的指針所描述的鎖,如果試圖獲取一把鎖,系統(tǒng)阻止給我們鎖則返回錯誤
F_SETLKW:如果請求鎖,因為其他進程在使用,則調(diào)用進程進入休眠,直到鎖可用被喚醒。
當一個進程終止時候,它所建立的所有鎖都會釋放,同樣關閉一個文件描述符,與該文件描述符相關的鎖都會釋放。
fork產(chǎn)生的子進程不繼承父進程設置的鎖。
- I/O多路轉(zhuǎn)接
1.對于從一個文件描述符讀,然后又寫另一個文件描述符這樣的操作,我們通常這樣寫
write(fd,buf,size);
}
這種阻塞I/O操作,我們經(jīng)常見,也是最低級的寫法,因為可能因為讀阻塞導致寫阻塞。這時候我們使用異步I/O,進程告訴內(nèi)核,當描述符準備好時候通過信號通知內(nèi)核,但是他也有限制,只有在描述符是
網(wǎng)絡或者終端設備時候才會起作用。
2.IO多路復用基本思想
3.實現(xiàn)函數(shù)select
select函數(shù)可以使我們執(zhí)行I/O多路轉(zhuǎn)接,通過傳給select函數(shù)的參數(shù)可以告訴內(nèi)核:
a.我們所關心的描述符
b.對于每個描述符我們所關心的條件,是否想從一個給定描述符讀/寫,是否關心描述符異常
c.愿意等待多長時間
也可以通過返回值得到以下信息
a.已經(jīng)準備好的文件描述符
b. 對于讀、寫、異常者三個條件中每一個,哪些已經(jīng)準備好
然后我們就可以使用read和write函數(shù)讀寫。
#include
#include
int select(int nfds,fd_set *read_fds,fd_set *write_fds,fd_set *except_fds,struct timeval *timeout);
參數(shù): nfds 所有監(jiān)控文件描述符最大的那一個 +1.(因為文件描述符編號從0開始,所以要加1)
read_fds 所有可讀的文件描述符集合。 沒有則為NULL
write_fds 所有可寫的文件描述符集合。 沒有則為NULL
except_fds 處于異常條件的文件描述符 沒有則為NULL
timeval: 超時設置。 NULL:一直阻塞,直到有文件描述符就緒或出錯
0 :僅僅監(jiān)測文件描述符集的狀態(tài),然后立即返回
非0 :在指定時間內(nèi),如果沒有事件發(fā)生,則超時返回
返回值:當timeval設置為NULL:返回值 -1 表示出錯
>0 表示集合中有多少個描述符準備好
當設置timeval非0時: 返回值 -1:表示出錯
>0: 表示集合中有多少描述符準備好
=0: 表示時間到了還沒有描述符準備好
對于fd_set數(shù)據(jù)類型有以下四種處理方式 fd:文件描述符、 fdset文件描述符集合
void FD_SET(int fd,fd_set *fdset): 將fd加入到fdest
void FD_CLR(int fd,fd_set *fdest): 將fd從fdest里面清除
void FD_ZERO(fd_set *fdest): 從fdest中清除所有文件描述符
void FD_ISSET(int fd,fd_set *fdest):判斷fd是否在fdest集合中
這些接口實現(xiàn)為宏或者函數(shù),調(diào)用 FD_ZERO 將fd_set變量的所有位置設置為0,如果要開啟描述符集合的某一位,可以調(diào)用 FD_SET ,調(diào)用FD_CLR 可以清除某一位,F(xiàn)D_ISSET用來檢測某一位是否打開。
在申明了一個描述符集合之后,必須使用FD_ZERO將其清零,下面是使用操作:
fd_set reset;
int fd;
FD_ZERO(&reset);
FD_SET(fd, &reset);
FD_ZERO(STDIN_FILENO, &reset);
if (FD_ISSET(fd, &reset)) {}
對于“準備好” 這個詞這里說明一下,什么才是準備好,什么是沒有準備好,如果對讀集(read_fds/write_fds) 中的一個描述符進行read/write操作沒有阻塞則認為是準備好,或者對except_fds有一個未決異常條件,則認為準備好。
一個描述符的阻塞并不影響整個select的阻塞。當文件描述符讀到文件結尾時候,read返回0.
4.實現(xiàn)函數(shù)poll
poll函數(shù)與select函數(shù)相似,不同的是,poll不是為每個條件(讀、寫、異常)構造一個文件描述符,而是構造一個pollfd結構數(shù)組,每個數(shù)組元素指定一個描述符編號,poll函數(shù)可以用于任何類型的文件描述符。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
參數(shù):fds:pollfd結構數(shù)組
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 請求事件 */
short revents; /* 返回事件 */
};
events:需要將events設置為以下一個或者多個值,這些值會告訴內(nèi)核哪些是我們關系的文件描述符
POLLIN 不阻塞地讀高優(yōu)先級數(shù)據(jù)意外的數(shù)據(jù)
POLLRDNORM 不阻塞地讀普通數(shù)據(jù)
POLLRDBAND 不阻塞地讀優(yōu)先級數(shù)據(jù)
POLLPRI 不阻塞地讀高優(yōu)先級數(shù)據(jù)
POLLOUT 普不阻塞地讀寫普通數(shù)據(jù)
POLLWRNORM 同POLLOUT
POLLWRBAND 不阻塞地寫低優(yōu)先級數(shù)據(jù)
POLLERR 發(fā)生錯誤
POLLHUP 發(fā)生掛起(當掛起后就不可以再寫該描述符,但是可以讀)
POLLNVAL 描述字不是一個打開的文件
revents:返回的文件描述符,用于說明描述符發(fā)生了哪些事件。
nfds:數(shù)組中元素數(shù)
timeout:等待時間
= -1:永遠等待,直到有一個描述符準備好,或者捕捉到一個信號,如果捕捉到信號返回-1。
= 0 :不等待,立即返回。這是輪詢的方法。
> 0: 等待的毫秒數(shù),有文件描述符準備好或者timeout超時立即返回。超時返回值為0.
5.散布讀和聚集寫
就是在一次函數(shù)調(diào)用中讀、寫多個非連續(xù)的緩沖區(qū)。
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
功能:散布讀
參數(shù):fd:文件描述符
iov:iovec結構指針
struct iovec {
void *iov_base; 緩沖地址
size_t iov_len; 緩沖大小
};
iovcnt:iov數(shù)組元素個數(shù)
返回值:成功:已讀個數(shù),失敗:-1
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
功能:聚集寫
參數(shù):fd:文件描述符
iov:iovec結構指針
struct iovec {
void *iov_base; 緩沖地址
size_t iov_len; 緩沖大小
};
iovcnt:iov數(shù)組元素個數(shù)
返回值:成功:已寫個數(shù),失敗:-1
6.存儲映射I/O
存儲映射I/O,將一個磁盤文件映射到內(nèi)存中的一個緩沖區(qū)上,從這個緩沖區(qū)讀寫數(shù)據(jù)就相當于讀寫文件數(shù)據(jù),就可以不再使用read、write。
void *mmap(void *addr,size_t len,int prot,int flags,int fd,off_t offset);
功能:將文件或設備空間映射到共享內(nèi)存區(qū),因此當從共享內(nèi)存讀數(shù)據(jù)時就相當于從文件中讀取數(shù)據(jù)
參數(shù): addr:要映射的起始地址,通常為NULL,讓內(nèi)核自動分配
len:映射到進程地址空間的字節(jié)數(shù)
port:映射區(qū)保護方式 PROT_READ 映射區(qū)可讀
PROT_WRITE 映射區(qū)可寫
PROC_EXEC 映射區(qū)可執(zhí)行
PROC_NONE 映射區(qū)不可訪問
flags: MAP_SHARED 變動是共享的
MAP_PRIVATE 變動是私有的
MAP_FIXED 準確解釋addr參數(shù), 如果不指定該參數(shù), 則會以4K大小的內(nèi)存進行對齊
MAP_ANONYMOUS 建立匿名映射區(qū), 不涉及文件
fd: 文件描述符,使用前必須先打開文件。
offset:從文件頭開始偏移量為0
p=(STU*)mmap(NULL,sizeof(STU)*5,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0) //STU* 數(shù)據(jù)類型
五、終端I/O
終端I/O系統(tǒng)是一個非常復雜的東西,我們不會去講解它,但是它有幾個非常重要的函數(shù)需要我們學,這幾個函數(shù)用來去嵌入式的串口編程
- 獲取/設置終端參數(shù)
#include
int tcgetattr(int fd, struct termios *termios_p);
功能:獲取終端屬性
參數(shù):fd:打開串口設備節(jié)點描述符
termios_p:終端屬性結構體指針
int tcsetattr(int fd, int optional_actions,
const struct termios *termios_p);
功能:設置終端屬性
參數(shù):fd:打開串口設備節(jié)點描述符
termios_p:終端屬性結構體指針:有70多種標志(這里不詳細介紹,后面會說)
optional_actions:TCSANOW: 更改立即發(fā)生
TCSADRAIIN: 發(fā)送所有輸出后更改才發(fā)生,更改輸出參數(shù)選用這個
TCSAFLUSH: 發(fā)送所有輸出后更改才發(fā)生,更改時所有未讀數(shù)據(jù)全部丟棄
- 波特率
#include
speed_t cfgetispeed(const struct termios *termios_p);
speed_t cfgetospeed(const struct termios *termios_p);
int cfsetispeed(struct termios *termios_p, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
功能:獲取/設置波特率
參數(shù):termios_p:struct termios結構體指針
speed:波特率:B50、B75、B110、B150、B200、B300、B600、B1200、B1800、B2400、B4800、B9600、B19200、B38400、B57600、B115200
返回值:成功0 失敗-1.
在調(diào)用cfget函數(shù)之前先調(diào)用tcgetattr函數(shù)獲取struct termios結構指針
- 控制函數(shù)
功能:沖洗緩沖區(qū)
參數(shù):fd:打開串口設備節(jié)點描述符
queue_selector:TCIFLUSH:沖洗輸入隊列
TCOFLUSH:沖洗輸出隊列
TCIOFLUSH:沖洗輸入和輸出緩沖隊列
返回值:成功0 失敗-1
int tcsendbreak(int fd, int duration);
功能:指定時間區(qū)間內(nèi)發(fā)送連續(xù)的0值位流
參數(shù):fd:打開串口設備節(jié)點描述符
duration: 0:傳遞延續(xù)0.25-0.5s
非0:傳遞時間依賴于實現(xiàn)
返回值:成功0 失敗-1
int tcdrain(int fd);
功能:等待所以輸出都被傳遞
返回值:成功0 失敗-1
int tcflow(int fd, int action);
功能:對輸入輸出流進行控制
參數(shù):action:TCOOFF:輸出被掛起
TCOON:啟動被掛起的輸出
TCIOOFF:發(fā)送一個stop,終端設備停止發(fā)送數(shù)據(jù)
TCION: 發(fā)送一個START,終端設備繼續(xù)發(fā)送數(shù)據(jù)
返回值:成功0 失敗-1
- Linux系統(tǒng)下串口編程實現(xiàn)demo
#include
#include
#include
#include
#include
#include
#include
#include
#define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0]))
#define MAX_BAUD 115200
#define UART_IDX "/dev/ttyUSB0"
#define CRC_OK 0
#define CRC_FAIL -1
typedef enum {
STANDARD_INPUT_MODE = 1,
RAWDATA_MODE
} uart_mode_e;
static int fd;
static pthread_t read_thread_id;
static int usb_thread_run;
static char recv_buf[1024 * 100];
static unsigned char recvmsg[1024 * 100];
static int ws_uart_send(char *buf, int len)
{
unsigned int total_byte = 0;
int send_byte;
while (len > 0) {
if (len < 1024)
send_byte = write(fd, buf+total_byte, len);
else
send_byte = write(fd, buf+total_byte, 1024);
if (send_byte < 0) {
tcflush(fd, TCOFLUSH);
printf("data send errorn");
return -1;
}
len -= send_byte;
total_byte += send_byte;
printf("len = %d total_byte = %dn", len, total_byte);
}
return 0;
}
static void *read_thread(void *arg)
{
int count,ret = 0;
int cnt;
int total;
char buf[64] = {0};
usb_thread_run = 1;
while (usb_thread_run) {
memset(recv_buf, 0, sizeof(recv_buf));
cnt = 0;
total = 0;
count = read(fd, buf, 64);
printf("cnt = %dn", count);
printf("buf = %sn", buf);
write(fd, buf, count);
}
return NULL;
}
/**
* @brief
* @note
* @param fd:
* @param baud_rate:
* @retval
*/
int set_uart_baud_rate(int fd, int baud_rate)
{
int ret = 0;
struct termios param;
int speed_arr[] = { B921600, B576000, B500000, B460800, B230400,B115200, B38400, B19200, B9600, B4800, B2400, B1200 };
int name_arr[] = { 921600, 576000, 500000, 460800, 230400,115200, 38400, 19200, 9600, 4800, 2400, 1200 };
int i = 0;
int status;
status = tcgetattr(fd, ¶m);
if(0 != status)
{
printf("%s:%d error = %dn",__func__, __LINE__, ret);
return ret;
}
for(i = 0; i < ARRAY_SIZE(speed_arr); i++)
{
if(baud_rate == name_arr[i])
{
tcflush(fd, TCIOFLUSH);
if(0 != cfsetispeed(¶m, speed_arr[i]))
{
printf("%s:%d error = %dn",__func__, __LINE__, ret);
return ret;
}
if(0 != cfsetospeed(¶m, speed_arr[i]))
{
printf("%s:%d error = %dn",__func__, __LINE__, ret);
return ret;
}
status = tcsetattr(fd, TCSANOW, ¶m);
if(0 != status)
{
printf("%s:%d error = %dn",__func__, __LINE__, ret);
return ret;
}
tcflush(fd, TCIOFLUSH);
break;
}
}
if(i == ARRAY_SIZE(speed_arr))
{
printf("%s:%d error = %dn",__func__, __LINE__, ret);
return ret;
}
return ret;
}
/**
* @brief
* @note
* @param fd:
* @param databits:
* @param stopbits:
* @param parity:
* @param mode:
* @retval
*/
int set_uart_parity(int fd, int databits, int stopbits, int parity, uart_mode_e mode)
{
int ret = 0;
struct termios param;
if(tcgetattr(fd, ¶m) != 0)
{
printf("%s:%d error = %dn",__func__, __LINE__, ret);
return ret;
}
param.c_cflag &= ~CSIZE;
switch(databits) /*設置數(shù)據(jù)位數(shù)*/
{
case 7:
param.c_cflag |= CS7;
break;
case 8:
param.c_cflag |= CS8;
break;
default:
return ret;
}
switch(parity)
{
case 'n':
case 'N':
param.c_cflag &= ~PARENB; /* Clear parity enable */
param.c_iflag &= ~(INPCK | ICRNL | IXON); /* Enable parity checking */
break;
case 'o':
case 'O':
param.c_cflag |= (PARODD | PARENB); /* 設置為奇效驗*/
param.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'e':
case 'E':
param.c_cflag |= PARENB; /* Enable parity */
param.c_cflag &= ~PARODD; /* 轉(zhuǎn)換為偶效驗*/
param.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'S':
case 's': /*as no parity*/
param.c_cflag &= ~PARENB;
param.c_cflag &= ~CSTOPB;
break;
default:
return ret;
}
/* 設置停止位*/
switch(stopbits)
{
case 1:
param.c_cflag &= ~CSTOPB;
break;
case 2:
param.c_cflag |= CSTOPB;
break;
default:
return ret;
}
if(mode == STANDARD_INPUT_MODE)
/*標準輸入設置*/
{
param.c_lflag &= ~(ECHO); //關閉回顯
param.c_lflag |= (ICANON);
param.c_oflag |= OPOST; //
}
/*raw data mode*/
else if(mode == RAWDATA_MODE)
{
param.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
param.c_oflag &= ~OPOST; //raw output
}
/* Set input parity option */
if(parity != 'n')
{
param.c_iflag |= INPCK;
}
param.c_cc[VTIME] = 10; //10 // 1 seconds
param.c_cc[VMIN] = 0;
tcflush(fd, TCIFLUSH);
/* Update the options and do it NOW */
if(tcsetattr(fd, TCSANOW, ¶m) != 0)
{
printf("%s:%d error = %dn",__func__, __LINE__, ret);
return ret;
}
return ret;
}
void uart_recv_start(void)
{
printf("recv start");
pthread_create(&read_thread_id, NULL, read_thread, NULL);
pthread_detach(read_thread_id);
}
void uart_deinit()
{
usb_thread_run = 0;
close(fd);
pthread_join(read_thread_id, NULL);
}
int main(int argc, char* argv[])
{
struct termios oldtio, newtio;
int ret = 0;
char buf[256];
fd = open(argv[1], O_RDWR | O_NOCTTY);
if (fd < 0) {
printf("Open %s failedn", argv[1]);
return -1;
} else
printf("Open %s successfullyn", argv[1]);
set_uart_baud_rate(fd, 115200);
ret = set_uart_parity(fd, 8, 1, 'n', RAWDATA_MODE);
uart_recv_start();
while (1) {
sleep(1);
}
}
-
驅(qū)動器
+關注
關注
53文章
8272瀏覽量
147075 -
數(shù)據(jù)
+關注
關注
8文章
7145瀏覽量
89591 -
內(nèi)存
+關注
關注
8文章
3055瀏覽量
74338 -
Linux系統(tǒng)
+關注
關注
4文章
596瀏覽量
27510
發(fā)布評論請先 登錄
相關推薦
linux下的一些文件的簡單操作
![<b class='flag-5'>linux</b><b class='flag-5'>下</b>的一些文件的簡單<b class='flag-5'>操作</b>](https://file.elecfans.com/web2/M00/8A/20/poYBAGO-Z2-AbGfOAABFEP7vWRs222.png)
Linux系統(tǒng)中網(wǎng)絡I/O性能改進方法的研究
Linux 系統(tǒng)應用編程之標準I/O詳解
需要了解Linux下的文件I/O編程
如何更改 Linux 的 I/O 調(diào)度器
![如何更改 <b class='flag-5'>Linux</b> 的 <b class='flag-5'>I</b>/<b class='flag-5'>O</b> 調(diào)度器](https://file.elecfans.com/web1/M00/92/3C/pIYBAFzbxcSAJnc-AAAiA0yZMo0368.png)
關于標準I/O庫執(zhí)行I/O操作
Linux中如何使用信號驅(qū)動式I/O?
![<b class='flag-5'>Linux</b>中如何使用信號驅(qū)動式<b class='flag-5'>I</b>/<b class='flag-5'>O</b>?](https://file.elecfans.com/web1/M00/E5/1E/pIYBAGBLFQ-AE-9YAAFrw9ppYpk269.png)
Linux磁盤I/O的性能指標和查看性能工具
深入理解 Linux 的 I/O 系統(tǒng)
![深入理解 <b class='flag-5'>Linux</b> 的 <b class='flag-5'>I</b>/<b class='flag-5'>O</b> <b class='flag-5'>系統(tǒng)</b>](https://file1.elecfans.com/web2/M00/88/B5/wKgZomRwDKqAPVInAAAxTcpD4cs307.png)
評論