在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Linux系統下I/O操作講解

科技綠洲 ? 來源:Linux開發架構之路 ? 作者:Linux開發架構之路 ? 2023-11-08 15:13 ? 次閱讀

Linux系統下I/O

一、I/O簡介

I/O(輸入/輸出)是在主存和外部設備(磁盤驅動器網絡、終端)之間復制數據的過程。輸入是從外部設備復制到主存,輸出是從主存復制到外部設備。

在Linux系統中所有的I/O設備都被映射稱為文件,所有的輸入輸出都被當做相應文件的讀和寫來執行,所以內核提供了系統級的I/O函數接口,使得所有輸入輸出都以統一且一致的方式來執行。

  1. 打開文件,返回一個非負整數,叫做描述符
  2. 每個進程都默認打開三個描述符,標準輸入 STDIN_FILENO(描述符0)、標準輸出 STDOUT_FILENO(描述符1)、標準出錯 STDERR_FILENO(描述符2)。
  3. 讀寫文件,讀就是從文件復制n個字節到內存,寫就是從內存復制n個字節到文件。
  4. 文件偏移:默認打開文件是從文件開頭起始的字節偏移量,可以使用seek來操作。
  5. 關閉文件。

今天從四個方面來說I/O,文件I/O、標準I/O庫、高級I/O、終端I/O。

  1. 文件I/O: 文件的打卡、讀寫、關閉、偏移。
  2. 標準I/O庫:Linux提供的標準I/O庫函數
  3. 高級I/O:非阻塞I/O、I/O多路轉接、異步I/O
  4. 終端I/O: 更改終端屬性操作的函數

二、文件I/O

Linux系統中文件I/O一般只用到以下五個函數:open、read、write、lseek、close。每次read、write都是一次系統調用(從用戶層拷貝到內核層再拷貝到用戶層)且不帶緩沖。

  1. 文件描述符

對于內核而言,每個打開的文件都是通過文件描述符引用的,每個文件描述符都是一個非負整數,打開或者創建一個文件都會返回一個文件描述符,通過這個文件描述符來進行讀寫,

  1. 打開/創建文件
#include
#include
#include

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:創建或者打開一個文件,返回一個文件描述符
參數: pathname:路徑名/文件名
flags:標志位
O_RDONLY 只讀

O_WRONLY 只寫

O_RDWR 既可以讀也可以寫

O_APPEND 以追加的方式操作文件

O_CREAT 如果文件不存在,則創建

O_TRUNC 如果文件存在,則清空文件的數據

O_EXCL 表示文件已經存在,而又重復創建一次,open函數會返回錯誤,
返回文件已經存在的錯誤,對錯誤做處理之后,直接打開文件就可以

O_APPEND 從文件末尾位置追加寫入

O_SYNC 每次write等物理I/O操作完成,包括由該write操作引起的文件屬性更新所需的I/O,(后邊會用到)

O_RSYNC 每個以文件描述符作為參數進行的read操作等待,直到所有對文件同一部分掛起的寫操作都完成。

mode:如果是創建一個文件,需要添加對應文件的屬性,模式屬性一般用一個八進制數代替,如果屬性成立,為1,不成立,則為0
rwxr-x-wx --> 0753
rw-rw-r-- --> 0664

返回值:成功:文件描述符
失敗:-1
  1. 關閉文件

關閉一個文件時會自動釋放加在該文件上的所有鎖,當進程終止時會自動關閉所有打開的文件。

#include

int close(int fd);
參數:fd:open返回的文件描述符
返回值:成功 0, 失敗 -1.
  1. 文件偏移

通常所有讀寫操作都是從當前文件偏移量處開始,并使偏移量增加讀寫的字節數,默認是0。可以使用lseek顯式打開文件設置偏移量。

#include
#include

off_t lseek(int fd, off_t offset, int whence);
參數:fd : open函數打開的文件
offset: 與whence有關
whence: 基準點
SEEK_SET 將讀寫位置指向文件頭后再增加offset個位移量。
SEEK_CUR 以目前的讀寫位置往后增加offset個位移量。
SEEK_END 將讀寫位置指向文件尾后再增加offset個位移量(使用該參數可以算出文件字節數)
當whence 值為SEEK_CUR 或SEEK_END時,參數offet允許負值的出現。
返回值:成功,返回文件偏移量,失敗 -1.
注釋:文件偏移量可以大于文件長度,這樣就會構成空洞文件,對于多出的這些字節被讀出為0.空洞文件在磁盤中不占用存儲區。
  1. 讀文件
#include

ssize_t read(int fd, void *buf, size_t count);
參數:
fd:文件描述符
buf:讀取到的數據
const:每一次最多讀取到的字節數
返回值:
成功:讀取的字節數 如果是0 代表結尾
失敗:-1
  1. 寫文件
#include

ssize_t write(int fd, const void *buf, size_t count);
功能:向一個文件描述符寫數據
參數:
fd:文件描述符
buf:要寫入的數據
const:每一次最多寫入到的字節數
返回值:
成功:寫入的字節個數
失敗:-1
失敗原因多是磁盤已滿或者超過一個給定進程的文件長度限制。
  1. 文件共享

Linux系統支持不同進程間共享打開文件,在此先說一下內核用于所以I/O的數據結構。

內核使用三種數據結構表示打開的文件,他們之間的關系決定了文件共享中一個進程對另一個進程的影響。 首先每個進程在進程表中有一個記錄項,每個記錄項包含一張打開的文件描述符,每個描述符占用一項,與文件描述符有關的是:

1.文件描述符標志

2.指向文件表項的指針

其次內核為每個打開文件維持一張文件表,文件表項包含:

1.文件狀態標志(讀、寫、阻塞等)

2.當前文件偏移量

3.指向該文件v節點表項指針     最后每個打開文件(設備)都有一個v節點結構,它包含了:

     1.文件類型

     2.對該文件進行各種操作的指針。

     3.i節點(i-node),包含了文件的長度、所以者、指向文件實際數據塊在磁盤的位置。

這些信息都是在打開文件時候從磁盤拷貝到內存,所以這些信息都是隨時可用的。總結一下這三張表關系

進程表項: fd標志
文件指針(文件表項):文件狀態標志
當前文件偏移量
v節點指針(v節點表項): v節點信息
v_data: i節點(i節點表項): i節點信息
當前文件長度

了解了內核的這三個數據結構之后我們回過頭來看文件共享。

假定一個進程打開了一個文件,返回文件描述符是4,另一個進程也打開了這個文件描述符返回的文件描述符是5,打開該文件的每個進程都有一個文件表項(進程對該文件的當前偏移量),但是該文件只有一個v節點。

  1. 每當write之后,文件表項中擔負起偏移量會增加寫入的字節數,如果當前文件偏移量超出了當前文件長度則i節點表項中文件長度也增加。
  2. 如果使用O_APPEND打開一個文件,相應的標志被設置到文件表項中的文件狀態標志,每次對該文件寫操作時,文件表項中當前文件偏移量會被設置為i節點表項的文件長度。
  3. 當使用lseek函數定位到文件尾端時候,文件表項中的當前文件偏移量被設置為i節點表項中的文件長度。
  4. 存在多個文件描述符指向同一個文件的情況。

需要C/C++ Linux服務器架構師學習資料加qun579733396獲取(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享

圖片

  1. 原子操作

當有多個進程操作一個文件時候為了數據同步Linux系統提供了原子操作。

1.open一個文件時候使用 O_APPEND 標志

2.使用pread 和 pwrite 函數 pread/pwrite 相當于調用lseek之后調用read/write,但是區別在于調用pread/pwrite時,無法中斷其定位和讀寫操作,而且不更新當前文件的偏移量。

#include

ssize_t pread(int fd, void *buf, size_t count, off_t offset);
功能:讀文件
參數:fd:文件描述符
buf:讀緩沖區
count:緩沖區大小
offset:偏移量
返回值: 成功:讀到的字節數,如果讀到文件尾返回0, 失敗-1

ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
功能:寫文件
參數:fd:文件描述符
buf:寫緩沖區
count:緩沖區大小
offset:偏移量
返回值: 成功:讀到的字節數, 失敗-1
  1. 將緩沖區數據寫到磁盤

在傳統Unix系統實現中大多數磁盤I/O通過緩沖區進行的,當我們向文件寫數據時,內核通常將數據復制到緩沖區中,之后再寫到磁盤,這種方式稱為延遲寫。下面函數將緩沖區數據寫入到磁盤。

#include

void sync(void);
將修改過的塊緩沖區排隊寫到隊列就返回,數據并不一定寫入到磁盤。命令sync就是調用sync函數。update系統守護進程每30s調用一次該函數。

int fsync(int fd);
只對一個文件描述符其作用,并且磁盤操作結束后才返回。

int fdatasync(int fd);
等同于fsync,但是同時更新文件屬性。
  1. 修改已打開的文件屬性
#include
#include

int fcntl(int fd, int cmd, ... /* arg */ );
功能:修改已打開文件屬性
參數:fd:文件描述符
cmd: F_DUPFD:復制文件描述符,新的文件描述符作為返回值返回。新文件描述符與舊fd共享同一文件表項,但是有自己的文件描述符標志,其FD_CLOEXXEC文件描述符標志被取消
F_DUPFD_CLOEXEC:復制文件描述符,設置與新文件描述符關聯的FD_CLOEXXEC文件描述符標志的值,返回新文件描述符
F_GETFD:對應于fd的文件描述符標志作為函數返回值
F_SETFD:對應fd設置文件描述符標志,新值為第三參數值
F_GETFL:對應fd的文件狀態標志作為函數返回值
F_SETFL:將文件狀態標志設置為第三個參數的值
F_GETOWN:獲取當前SIGIO和SIGURG信號的進程ID和組ID
F_SETOWN:設置接收SIGIO和SIGURG信號的進程ID和組ID
第三參數:總是一個整數,一般0
返回值:出錯:-1
成功:其他
  1. ioctl 函數

ioctl函數是I/O操作的萬金油,內核對設備的IO通道控制操作函數,多用于驅動程序。

#include

int ioctl(int fd, int request, ...);
參數:@fd :文件描述符的序號
@request :請求 代表不同操作的數字值
@... :可變參數,(寫或者不寫根據請求決定)
:傳遞的是整數,或者地址
返回值:出錯:-1
成功:其他

ioctl函數的實現需要一種命令碼
32位
比特位 含義
31 - 30 00 : 命令不帶參數
01 : 命令從驅動中獲取數據,讀方向
10 : 命令把數據寫入驅動,寫方向
11 : 命令即寫又讀:雙向
29 - 16 類型的大小
15 - 8 類型
7 - 0 序號

三 、標準I/O庫

標志I/O庫處理了很多細節,比如緩沖區的分配、優化塊長度執行I/O等,更方便大家進行I/O操作

在前面說的I/O函數都是圍繞著文件描述符進行操作的,在標準I/O庫里對應的是 流 進行操作的,當打開一個一個流時,標準I/O庫函數fopen返回一個指向FILE對象的指針。它是一個結構體包含了標準I/O庫

所管理該流的所有信息,包括用于實際I/O的文件描述符、指向用于該流的緩沖區指針、緩沖區長度、以及當前緩沖區中的字符等。

對應文件描述符每個進程定義了三個流,標準輸入(stdin)、標準輸出(stdout)、標準出錯(stderr)

2.緩沖區

標準I/O庫提供緩沖區的目的是為了盡可能減少使用read和write(太消耗資源了),它對每個I/O流自動地進行緩沖管理,庫函數提供的接口,在內存中創建一塊緩沖區,直到滿足一定條件,才會真正寫入,本質上還是系統調用,可以在不同系統間進行數據傳輸。

有以下三種緩沖

1.全緩沖,操作的文件,3個條件:

  1. 緩沖區滿,則會刷新緩沖區 4096byte
  2. 程序正常結束
  3. fflush刷新緩沖區(將內容寫到磁盤,在驅動程序表示丟棄緩沖區數據)

2.行緩沖:指針對終端進行操作,4個條件:

  1. 緩沖區滿,則會刷新緩沖區 1024byte
  2. 程序正常結束
  3. fflush刷新緩沖區
  4. “n”

3.無緩沖:指針終端進行操作

修改系統默認緩沖(一定要在流打開之后修改)

#include

void setbuf(FILE *stream, char *buf);
功能:打開或者關閉緩沖機制
參數:stream:打開的流
buf:指向一個長度為BUFSIZE的緩沖區,設置為null則關閉緩沖
返回值:成功0,失敗非0

int setvbuf(FILE *stream, char *buf, int mode, size_t size);
功能:打開或者關閉緩沖機制
參數:stream:打開的流
buf:指向一個長度為BUFSIZE的緩沖區,設置為null則系統自動分配
mode:_IONBF :無緩沖,此選項可以忽略buf和size
_IOLBF :行緩沖
_IOFBF :全緩沖
返回值:成功0,失敗非0

刷新緩沖區,將所有未寫的數據傳輸到內核。如果stream為null,則刷新所有緩沖區。

#include

int fflush(FILE *stream);
  1. 打開流

打開一個流默認是全緩沖,當打開終端設備時候默認為行緩沖。

#include

FILE *fopen(const char *path, const char *mode);
功能:打開一個標準I/O流
參數:path:文件名
mode:打開模式 (b:二進制文件)
r/rb:打開文件對文件進行讀操作,文件必須存在,
r+/r+b/rb+:打開文件對文件進行讀寫操作,文件必須存在
w/wb:打開或者創建文件,對文件進行寫入
w+/w+b/wb+:打開或者創建文件,對文件進行讀寫操作
a/ab:打開或者創建文件,從文件末尾位置追加數據(多個進程追加一個文件也可以正確寫入)
a+/a+b/ab+:打開或者創建文件,從文件末尾進行讀取、追加文件。如果文件不存在創建文件,從文件起始處讀寫。

返回值:成功返回文件指針,失敗返回null

FILE *fdopen(int fd, const char *mode);
功能:取一個文件描述符,并使標準I/O流與之相關聯,此函數常用于由創建管道和網絡通信管道函數返回的描述符。因為這些特色文件不能用fopen打開。
返回值:成功返回文件指針,失敗返回null

FILE *freopen(const char *path, const char *mode, FILE *stream);
功能:在一個指定流上打開一個文件,如果已經打開則先關閉再打開,此函數一般用于將一個文件打開為一個預定義的流:stdin、stdout、stderr
參數:path:文件名
mode:
返回值:成功返回文件指針,失敗返回null
  1. 關閉流

當關閉一個流時候,緩沖區所有數據都被丟棄。

#include

int fclose(FILE *fp);
返回值:成功0,失敗EOF(-1)
  1. 讀流和寫流

每次打開一個I/O可以使用三種不同方式進程讀寫流 1. 每次讀寫一個字符的I/O 2. 每次讀寫一行的I/O,沒次以換行符終止 3. 直接I/O,直接讀寫某種指定長度的對象,常用于二進制和結構體讀寫。

讀寫一個字符

#include

int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
功能:讀取數據
參數:流
返回值: 成功 讀取的字符,失敗 -1(EOF)
區別:getc為宏。fgetc為函數,所以fgetc可以當做地址作為參數傳遞,getc不可以。

#include

int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
功能:寫入文件數據
參數:c 寫入的字符 stream 流
返回值:成功 寫入的字符,失敗 EOF
  1. 讀寫一行字符
#include
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);(不推薦使用,因為無法指定長度,可以造成緩沖區溢出)
功能:讀取文件中的一行字符,遇到n 結束
參數:s 指向用戶開辟的緩沖區,實現定義一個數組
size:要求讀取字節個數
stream:流
返回值:成功 讀取的字符串,失敗 EOF;

#include

int fputs(const char *s, FILE *stream);
int puts(const char *s);
功能:輸出以null結尾的字符串數據數據到指定文件中
參數:s 指定要被讀取數據的緩沖區
輸出 n 但不能輸出?
返回值:成功 讀取的字符串,失敗 EOF;
  1. 二進制I/O讀寫
#include
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:讀文件
參數:ptr:事先定義的變量,需要傳遞變量的
size:每個對象的大小
number:對象個數
stream:流
返回值:成功:返回實際讀取到對象的個數
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);
功能:寫文件
參數:ptr:事先定義的變量,需要傳遞變量的
size:每個對象的大小
number:對象個數
stream:流
返回值:成功:返回實際寫對象的個數
注釋:這兩個函數存在一個問題就是只能讀寫同一系統上的數據,如果是不同系統則會造成問題。因為在不同系統同一結構的同一成員偏移量可能不同。
  1. 定位流
#include

int fseek(FILE *stream, long offset, int whence);
功能:文件定位
參數:stream 流
offset:偏移量
whence:基準點
SEEK_SET 文件開頭位置
SEEK_CUR 文件當前位置
SEEK_END 文件末尾位置
從后往前偏移加 - 號
返回值:成功 0 失敗 -1

long ftell(FILE *stream);
功能 返回當前文件位置指針的位置是在那個地址,使用數字的形式表示
參數:stream 流
返回值:成功返回文件當前位置,出錯-1.

void rewind(FILE *stream);
參數:stream 流
功能: 把文件指針指向開頭

int fgetpos(FILE *stream, fpos_t *pos);
功能:將文件位置指示器的當前值存入pos指向的對象中

int fsetpos(FILE *stream, fpos_t *pos);
功能:將文件位置定位到pos指示的值位置。
  1. 格式化I/O
格式化輸出

#include

int printf(const char *format, ...);
功能:發送格式化輸出到標準輸出 stdout。
參數:format -- 這是字符串,包含了要被寫入到標準輸出 stdout 的文本

int fprintf(FILE *stream, const char *format, ...);
功能:寫入到指定的流。

int sprintf(char *str, const char *format, ...);
功能:將格式化字符串寫入到str中,自動會加一個null字節
參數:str:保存格式化的字符串
format -- 這是字符串
返回值:成功:返回寫入到str中字符數(不包含null),失敗負數

int snprintf(char *str, size_t size, const char *format, ...);
功能:同sprintf,但是sprintf可能會造成緩沖區溢出功能,所以snprintf會限定寫入字節數。
參數:str:保存格式化的字符串,自動會加一個null字節
size:字符串大小
format -- 這是字符串
返回值:如果格式化后的字符串長度小于等于 size,則會把字符串全部復制到 str 中,并給其后添加一個字符串結束符 ?;
如果格式化后的字符串長度大于 size,超過 size 的部分會被截斷,只將其中的 (size-1) 個字符復制到 str 中,并給其后添加一個字符串結束符 ?,返回值為欲寫入的字符串長度。
失敗:負數
格式字符: %h:輸出short型
%d 十進制有符號整數
%md:m為指定的輸出字段的寬度。如果數據的位數小于m,則左端補以空格,若大于m,則按實際位數輸出。
%ld:輸出長整型數據。
%lld: long long型
%u 十進制無符號整數
%f 浮點數 輸出float
%lf 浮點數 輸出double
%m.nf:輸出共占m列,其中有n位小數,如數值寬度小于m左端補空格。
%-m.nf:輸出共占m列,其中有n位小數,如數值寬度小于m右端補空格。
%s 字符串
%c 單個字符
%p 指針的值
%% 百分號本身
%e 指數形式的浮點數
%x, %X 無符號以十六進制表示的整數
%o 無符號以八進制表示的整數
%g(%G) 浮點數不顯無意義的零"0"
%p 輸出地址符
%lu 32位無符號整數
%llu 64位無符號整數
附加格式說明符
m 輸出數據域寬,數據長度 .n 對實數,指定小數點后位數(四舍五入)
- 輸出數據在域內左對齊(缺省右對齊)

+ 指定在有符號數的正數前顯示正號(+)
0 輸出數值時指定左面不使用的空位置自動填0
# 在八進制和十六進制數前顯示前導0,0x
l long類型輸出 %ld
double類型輸出 %lf

格式化輸入:
#include

int scanf(const char *format, ...);
功能:按照格式從終端輸入數據
參數:
format:格式控制串
%d 十進制整數
%c 字符數據
%s 字符串
%f 浮點類型

arg:可變參
如果要將輸入的數據保存在arg變量里面,需要傳arg的地址
返回值:成功:輸入的個數 失敗EOF

int fscanf(FILE *stream, const char *format, ...);
功能:從流 stream 讀取格式化輸入
參數:stream :這是指向 FILE 對象的指針,該 FILE 對象標識了流。
format :這是 C 字符串,包含了以下各項中的一個或多個:空格字符、非空格字符 和 format 說明符。
返回值:如果成功,該函數返回成功匹配和賦值的個數。如果到達文件末尾或發生讀錯誤,則返回 EOF。

int sscanf(const char *str, const char *format, ...);
功能:從字符串讀取格式化輸入。
參數:str:這是 C 字符串,是函數檢索數據的源。
format :這是 C 字符串,包含了以下各項中的一個或多個:空格字符、非空格字符 和 format 說明符
返回值:如果成功,該函數返回成功匹配和賦值的個數。如果到達文件末尾或發生讀錯誤,則返回 EOF。

格式字符:同格式化輸出,左補空格;否則按實際輸出
  1. 臨時文件
#include

char *tmpnam(char *s);
功能:產生一個與現有文件不同名的文件,每次調用都會產生不同路徑的臨時文件
參數:保存返回的路徑名
返回值:返回文件路徑名

#include

FILE *tmpfile(void);
功能:產生一個臨時二進制文件(wb+),關閉該文件時會自動刪除該文件
參數:保存返回的路徑名
返回值:返回文件路徑名

11 內存流

標準I/O庫都是是將文件中數據取出來緩沖在內存中,現在我們可以直接通過緩沖區與主存直接來回傳遞數據,不依賴文件。仍然使用FILE指針,這些流看起來像文件流,其實是內存流。

內存流不訪問文件只訪問主存,所以如果標準I/O流作為參數用于臨時文件的話,用內存流替代會有很大性能提高。

#include

FILE *fmemopen(void *buf, size_t size, const char *mode);
功能:內存流創建
參數:buf:指向緩沖區的開始位置,如果為null,讀寫都沒有任何意義。
size:指定緩沖區大小的字節數,如果buf為null,則自動分配大小
mode:同fopen的mode
返回值:成功 返回流指針,失敗null

FILE *open_memstream(char **ptr, size_t *sizeloc);
功能:創建流面向字節

#include

FILE *open_wmemstream(wchar_t **ptr, size_t *sizeloc);
功能:創建流面向寬字節

四、高級I/O

非阻塞I/O、I/O多路轉接、異步I/O、記錄鎖,這些都會在進程間通信用到

  1. 非阻塞I/O

對于給定的文件描述符,有兩種方法指定為非阻塞I/O。

  1. 調用open獲得描述符時候指定 O_NONBLOCK標志
  2. 對于打開的文件描述符,調用fcntl函數,將O_NONBLOCK標志打開
  3. 記錄鎖

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

int fcntl(int fd, int cmd, ... /* arg */ );
對于記錄鎖,cmd的參數為 F_GETKL、F_SETLK、F_SETLKW。第三個參數為指向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; 加鎖或者解鎖的區域起始偏移量
off_t l_len; 區域長度
pid_t l_pid; 持有鎖阻塞當前的進程
};
如果len為0,表示鎖的范圍無限大,不管向文件追加多少數據都在鎖范圍內。
對整個文件加鎖,len=0,whence=EEK_SET。
共享讀鎖:任意多個進程可以在給定字節上有一把共享讀鎖,
獨占性寫鎖:如果給定字節已經有寫鎖,那么不可再加任何鎖。
F_GETKL:判斷由flock結構的指針所描述的鎖是否會被另外一把鎖排斥。如果存在一把鎖,它阻止創建由flock結構的指針所描述的鎖,如果不存在則吧type修改為F_UNLCK
F_SETLK:由flock結構的指針所描述的鎖,如果試圖獲取一把鎖,系統阻止給我們鎖則返回錯誤
F_SETLKW:如果請求鎖,因為其他進程在使用,則調用進程進入休眠,直到鎖可用被喚醒。

當一個進程終止時候,它所建立的所有鎖都會釋放,同樣關閉一個文件描述符,與該文件描述符相關的鎖都會釋放。
fork產生的子進程不繼承父進程設置的鎖。
  1. I/O多路轉接

1.對于從一個文件描述符讀,然后又寫另一個文件描述符這樣的操作,我們通常這樣寫

while(read(fd,buf,size)) {
write(fd,buf,size);
}

這種阻塞I/O操作,我們經常見,也是最低級的寫法,因為可能因為讀阻塞導致寫阻塞。這時候我們使用異步I/O,進程告訴內核,當描述符準備好時候通過信號通知內核,但是他也有限制,只有在描述符是

網絡或者終端設備時候才會起作用。

2.IO多路復用基本思想

先構造一張有關描述符的表,然后調用一個函數,當這些文件描述符中的一個或多個已準備好進行IO時函數才返回,函數返回時告訴進程已經有描述符就緒,可以進行IO操作。

3.實現函數select

select函數可以使我們執行I/O多路轉接,通過傳給select函數的參數可以告訴內核:

a.我們所關心的描述符

b.對于每個描述符我們所關心的條件,是否想從一個給定描述符讀/寫,是否關心描述符異常

c.愿意等待多長時間

也可以通過返回值得到以下信息

a.已經準備好的文件描述符

b. 對于讀、寫、異常者三個條件中每一個,哪些已經準備好

然后我們就可以使用read和write函數讀寫。

#include
#include
#include

int select(int nfds,fd_set *read_fds,fd_set *write_fds,fd_set *except_fds,struct timeval *timeout);
參數: nfds 所有監控文件描述符最大的那一個 +1.(因為文件描述符編號從0開始,所以要加1)
read_fds 所有可讀的文件描述符集合。 沒有則為NULL
write_fds 所有可寫的文件描述符集合。 沒有則為NULL
except_fds 處于異常條件的文件描述符 沒有則為NULL
timeval: 超時設置。 NULL:一直阻塞,直到有文件描述符就緒或出錯
0 :僅僅監測文件描述符集的狀態,然后立即返回
非0 :在指定時間內,如果沒有事件發生,則超時返回
返回值:當timeval設置為NULL:返回值 -1 表示出錯
>0 表示集合中有多少個描述符準備好
當設置timeval非0時: 返回值 -1:表示出錯
>0: 表示集合中有多少描述符準備好
=0: 表示時間到了還沒有描述符準備好

對于fd_set數據類型有以下四種處理方式 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集合中
這些接口實現為宏或者函數,調用 FD_ZERO 將fd_set變量的所有位置設置為0,如果要開啟描述符集合的某一位,可以調用 FD_SET ,調用FD_CLR 可以清除某一位,FD_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.實現函數poll

poll函數與select函數相似,不同的是,poll不是為每個條件(讀、寫、異常)構造一個文件描述符,而是構造一個pollfd結構數組,每個數組元素指定一個描述符編號,poll函數可以用于任何類型的文件描述符。

#include

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
參數:fds:pollfd結構數組
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 請求事件 */
short revents; /* 返回事件 */
};
events:需要將events設置為以下一個或者多個值,這些值會告訴內核哪些是我們關系的文件描述符
POLLIN 不阻塞地讀高優先級數據意外的數據
POLLRDNORM 不阻塞地讀普通數據
POLLRDBAND 不阻塞地讀優先級數據
POLLPRI 不阻塞地讀高優先級數據
POLLOUT 普不阻塞地讀寫普通數據
POLLWRNORM 同POLLOUT
POLLWRBAND 不阻塞地寫低優先級數據
POLLERR 發生錯誤
POLLHUP 發生掛起(當掛起后就不可以再寫該描述符,但是可以讀)
POLLNVAL 描述字不是一個打開的文件
revents:返回的文件描述符,用于說明描述符發生了哪些事件。
nfds:數組中元素數
timeout:等待時間
= -1:永遠等待,直到有一個描述符準備好,或者捕捉到一個信號,如果捕捉到信號返回-1。
= 0 :不等待,立即返回。這是輪詢的方法。
> 0: 等待的毫秒數,有文件描述符準備好或者timeout超時立即返回。超時返回值為0.

5.散布讀和聚集寫

就是在一次函數調用中讀、寫多個非連續的緩沖區。

#include

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
功能:散布讀
參數:fd:文件描述符
iov:iovec結構指針
struct iovec {
void *iov_base; 緩沖地址
size_t iov_len; 緩沖大小
};
iovcnt:iov數組元素個數
返回值:成功:已讀個數,失敗:-1

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
功能:聚集寫
參數:fd:文件描述符
iov:iovec結構指針
struct iovec {
void *iov_base; 緩沖地址
size_t iov_len; 緩沖大小
};
iovcnt:iov數組元素個數
返回值:成功:已寫個數,失敗:-1

6.存儲映射I/O

存儲映射I/O,將一個磁盤文件映射到內存中的一個緩沖區上,從這個緩沖區讀寫數據就相當于讀寫文件數據,就可以不再使用read、write。

#include
void *mmap(void *addr,size_t len,int prot,int flags,int fd,off_t offset);
功能:將文件或設備空間映射到共享內存區,因此當從共享內存讀數據時就相當于從文件中讀取數據
參數: addr:要映射的起始地址,通常為NULL,讓內核自動分配
len:映射到進程地址空間的字節數
port:映射區保護方式 PROT_READ 映射區可讀
PROT_WRITE 映射區可寫
PROC_EXEC 映射區可執行
PROC_NONE 映射區不可訪問

flags: MAP_SHARED 變動是共享的
MAP_PRIVATE 變動是私有的
MAP_FIXED 準確解釋addr參數, 如果不指定該參數, 則會以4K大小的內存進行對齊
MAP_ANONYMOUS 建立匿名映射區, 不涉及文件
fd: 文件描述符,使用前必須先打開文件。
offset:從文件頭開始偏移量為0

p=(STU*)mmap(NULL,sizeof(STU)*5,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0) //STU* 數據類型

五、終端I/O

終端I/O系統是一個非常復雜的東西,我們不會去講解它,但是它有幾個非常重要的函數需要我們學,這幾個函數用來去嵌入式的串口編程

  1. 獲取/設置終端參數
#include
#include

int tcgetattr(int fd, struct termios *termios_p);
功能:獲取終端屬性
參數:fd:打開串口設備節點描述符
termios_p:終端屬性結構體指針

int tcsetattr(int fd, int optional_actions,
const struct termios *termios_p);
功能:設置終端屬性
參數:fd:打開串口設備節點描述符
termios_p:終端屬性結構體指針:有70多種標志(這里不詳細介紹,后面會說)
optional_actions:TCSANOW: 更改立即發生
TCSADRAIIN: 發送所有輸出后更改才發生,更改輸出參數選用這個
TCSAFLUSH: 發送所有輸出后更改才發生,更改時所有未讀數據全部丟棄
  1. 波特率
#include
#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);
功能:獲取/設置波特率
參數:termios_p:struct termios結構體指針
speed:波特率:B50、B75、B110、B150、B200、B300、B600、B1200、B1800、B2400、B4800、B9600、B19200、B38400、B57600、B115200
返回值:成功0 失敗-1.

在調用cfget函數之前先調用tcgetattr函數獲取struct termios結構指針
  1. 控制函數
int tcflush(int fd, int queue_selector);
功能:沖洗緩沖區
參數:fd:打開串口設備節點描述符
queue_selector:TCIFLUSH:沖洗輸入隊列
TCOFLUSH:沖洗輸出隊列
TCIOFLUSH:沖洗輸入和輸出緩沖隊列
返回值:成功0 失敗-1

int tcsendbreak(int fd, int duration);
功能:指定時間區間內發送連續的0值位流
參數:fd:打開串口設備節點描述符
duration: 0:傳遞延續0.25-0.5s
非0:傳遞時間依賴于實現
返回值:成功0 失敗-1

int tcdrain(int fd);
功能:等待所以輸出都被傳遞
返回值:成功0 失敗-1

int tcflow(int fd, int action);
功能:對輸入輸出流進行控制
參數:action:TCOOFF:輸出被掛起
TCOON:啟動被掛起的輸出
TCIOOFF:發送一個stop,終端設備停止發送數據
TCION: 發送一個START,終端設備繼續發送數據
返回值:成功0 失敗-1
  1. Linux系統下串口編程實現demo
#include
#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) /*設置數據位數*/
{
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; /* 轉換為偶效驗*/
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);
}

}
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 驅動器
    +關注

    關注

    53

    文章

    8288

    瀏覽量

    147139
  • 數據
    +關注

    關注

    8

    文章

    7167

    瀏覽量

    89691
  • 內存
    +關注

    關注

    8

    文章

    3063

    瀏覽量

    74374
  • Linux系統
    +關注

    關注

    4

    文章

    596

    瀏覽量

    27524
收藏 人收藏

    評論

    相關推薦

    linux的一些文件的簡單操作

    Linux 應用編程中最需要掌握的基礎就是文件 I/O操作,學習過linux或者有過了解的應該都會聽過一句話:
    發表于 01-11 15:40 ?597次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>下</b>的一些文件的簡單<b class='flag-5'>操作</b>

    Linux系統中網絡I/O性能改進方法的研究

    選擇并設計高效的網絡I/O模型是改善服務器性能的關鍵。該文通過對Linux系統中幾種網絡I/O
    發表于 04-09 09:41 ?28次下載

    Linux 系統應用編程之標準I/O詳解

    本章前面幾節所述的文件及I/O讀寫都是基于文件描述符的。這些都是基本的I/O控制,是不帶緩存的。而本節所要討論的I/
    發表于 10-18 15:45 ?0次下載

    需要了解Linux的文件I/O編程

    linuxC語言對于文件的操作,我們會經常用到fopen(),fclose(),fwrite(),fread(),fgets()等一系列庫函數,基本和是和windows下學習C語言一樣的,其實這些庫函數就是在linuxx
    發表于 05-12 10:09 ?580次閱讀

    如何更改 LinuxI/O 調度器

    LinuxI/O 調度器是一個以塊式 I/O 訪問存儲卷的進程,有時也叫磁盤調度器。Linux
    發表于 05-15 15:54 ?878次閱讀
    如何更改 <b class='flag-5'>Linux</b> 的 <b class='flag-5'>I</b>/<b class='flag-5'>O</b> 調度器

    關于標準I/O庫執行I/O操作

    當在輸入和輸出中遇到換行符時,標準I/O庫執行I/O操作。這允許我們一次輸出一個字符,但只有在寫了一行之后才進行實際
    的頭像 發表于 07-01 17:17 ?2432次閱讀

    Linux操作系統知識講解:走進內存

    Linux操作系統知識講解:走進內存
    的頭像 發表于 08-28 10:30 ?2410次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>操作系統</b>知識<b class='flag-5'>講解</b>:走進內存

    Linux操作系統知識講解:走進linux 內存地址空間

    Linux操作系統知識講解:走進linux 內存地址空間
    的頭像 發表于 08-28 10:45 ?5117次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>操作系統</b>知識<b class='flag-5'>講解</b>:走進<b class='flag-5'>linux</b> 內存地址空間

    Linux操作系統知識講解:走進Linux 內存分配算法

    Linux操作系統知識講解:走進Linux 內存分配算法
    的頭像 發表于 08-28 10:57 ?5517次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>操作系統</b>知識<b class='flag-5'>講解</b>:走進<b class='flag-5'>Linux</b> 內存分配算法

    Linux操作系統知識講解:走進Linux 內存使用場景

    Linux操作系統知識講解:走進Linux 內存使用場景
    的頭像 發表于 08-28 11:04 ?3028次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>操作系統</b>知識<b class='flag-5'>講解</b>:走進<b class='flag-5'>Linux</b> 內存使用場景

    Linux操作系統知識講解:避免內存使用七大坑

    Linux操作系統知識講解:避免內存使用七大坑
    的頭像 發表于 08-28 11:12 ?2898次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>操作系統</b>知識<b class='flag-5'>講解</b>:避免內存使用七大坑

    Linux中如何使用信號驅動式I/O

    I/O系統調用可能因為無法立即完成而被操作系統掛起,直到等待的事件發生為止。 點擊查看大圖 非阻塞式 I/
    的頭像 發表于 03-12 14:47 ?2493次閱讀
    <b class='flag-5'>Linux</b>中如何使用信號驅動式<b class='flag-5'>I</b>/<b class='flag-5'>O</b>?

    Linux磁盤I/O的性能指標和查看性能工具

    在我之前的文章:《探討 Linux 的磁盤 I/O》中,我談到了 Linux 磁盤 I/O 的工
    的頭像 發表于 05-14 15:21 ?2695次閱讀

    深入理解 LinuxI/O 系統

    傳統的 System Call I/OLinux 系統中,傳統的訪問方式是通過 write() 和 read() 兩個系統調用實現的,
    發表于 05-26 09:31 ?413次閱讀
    深入理解 <b class='flag-5'>Linux</b> 的 <b class='flag-5'>I</b>/<b class='flag-5'>O</b> <b class='flag-5'>系統</b>

    Linux I/O 接口的類型及處理流程

    Linux I/O 接口 Linux I/O 接口可以分為以下幾種類型: 文件
    的頭像 發表于 11-08 16:43 ?1040次閱讀
    <b class='flag-5'>Linux</b> <b class='flag-5'>I</b>/<b class='flag-5'>O</b> 接口的類型及處理流程
    主站蜘蛛池模板: xxx69日本hd| 欧美日韩一区二区三区视频 | 97人洗澡人人澡人人爽 | 国产玖玖在线 | 黄色网址视频在线观看 | 农村一级片 | 亚洲日本精品 | 夜夜bb | 1314酒色| 欧美午夜寂寞影院安卓列表 | 一区二区三区中文字幕 | 免费中国jlzzjlzz在线播放 | 久久午夜国产片 | 国产美女激情视频 | 亚洲一区二区影视 | 国产精品久久久福利 | 黄色大片a级 | 国产成人黄网址在线视频 | 欧美性另类 | 久久国产午夜精品理论篇小说 | 一级做a爱片特黄在线观看免费看 | 玖玖精品国产 | 午夜嘿嘿| 亚洲视频在线一区二区 | 成人免费看黄网站无遮挡 | 亚洲一区高清 | 可以免费看黄的网站 | 黑人黄色大片 | 久青草免费视频手机在线观看 | 国产一区在线mmai | 色偷偷成人 | 中文日产国产精品久久 | 午夜在线一区 | 小屁孩cao大人免费网站 | 2017av在线| 欧美久久天天综合香蕉伊 | 一区在线视频 | 日本黄色小视频网站 | 性夜影院爽黄a爽免费视频 性瘾高h姚蕊全文免费阅读 | 午夜欧美精品久久久久久久久 | 欧美另类高清 |