Linux高级篇——IO系统编程
1.文件IO
2.標準IO
3.動靜態庫的制作
4.目錄IO
1.文件IO
文件IO簡介
涉及哪些接口?
Input ,Output
是從用戶空間角度考慮的輸入與輸出:
從內核讀取數據或從文件中讀取數據,叫:input read函數
寫數據到內核或寫數據到文件中,叫:output write函數
內核中有很多文件,應該寫到哪一個文件中呢,或從哪一個文件中讀呢?
(內核應該負責管理這些文件-文件管理)
因此在寫入或讀出之前用戶應該指定某個文件,即要創建或打開某個文件
即:read ,write 這二個函數應該有一個參數是指定某個文件
Read或write 函數之前有一個函數: open
操作之后,還要關閉這些文件 close
open – 打開或創建一個文件
open(char *, flag, mode)在fcntl.h文件中聲明。函數的作用:創建或打開某個文件,參數:最多有三個參數;
第一個參數,char * 包含有文件名和路徑
第二個參數:flag 打開文件的方式
第三個參數:mode 創建文件的權限。
?flag內容如下:
flag 功能
O_RDONLY 只讀
O_WRONLY 只寫
O_RDWR 讀寫
O_CREAT 創建一個文件
O_EXCL 如果使用O_CREAT時文件存在,則可返回錯誤消息(返回-1)。如果沒使用O_CREAT,那么不管打開的文件存不存在,都會創建一個新的文件,這一 參數可測試文件是否存在。
O_TRUNC 打開文件(會把已經存在的內容給刪除)。
O_APPEND 追加方式打開文件(不會把已經存在的內容給刪除)。
?返回值:
成功:文件描述符,它是一個非負的正整數,即文件的ID號,相當于人的身份證號;
出錯:-1。
Open 函數創建文件時的權限是:
mode & (~umask)
B111 111 111 & ~(B 000 010 010) = B 111 101 101
umask是掩碼,我們可以通過
umask 掩碼值(4位八進制)來修改
什么是文件描述符?
內核的一個重要功能是文件管理,系統有非常多的文件,內核怎樣認識每一個文件呢?內核采用ID號的方式標識這些文件,inode 號,node號表示不同的文件,比如ls –lai i,在一個進程下只要文件不一樣,inode號就不一樣。
那么這些內核的文件的ID號,在每個用戶的程序中怎樣映射的呢?即是文件描述符。
open函數的返回值就是這個ID號
ID號有什么規律呢?
從0開始累加,
程序進行時(進程),內核會自動打開3個文件描述符,0,1,2,分別對應,標準輸入、輸出和出錯,這樣在程序中,每打開一個文件,文件描述符值從3開始累加。
write(int fd, void *buf, size_t count ):
第一個參數:向哪一個文件中去寫;第二個參數:向這個文件中寫什么內容;第三個參數:向這個文件中寫多少個。函數的返回值:是實際寫的字節數。
返回值:是實際寫的字節數
read(int fd, void *buf, size_t count)
第一個參數:從哪一個文件中去讀;第二個參數:讀到什么地方去;第三個參數:讀多少個。函數的返回值:是實際讀的字節數。
返回值:是實際讀的字節數
上面這個程序是無法把程序讀出來的,因為我們在對文件進行寫操作的時候,文件指針已經指向最后,讀的時候會接著文件指針讀,所以就讀不出來東西
leek(int fd, off_t offset, int whence),
該函數的頭文件:sys/types.h unistd.h;
功能:調整讀寫的位置指針;
第一個參數:要調整的文件的文件描述符;
第二個參數:偏移量,每一讀寫操作所需要移動的距離,單位是字節的數量,可正可負(向前移,向后移);
第三個參數:當前位置的基點,有三個標志,
SEEK_SET:當前位置為文件的開頭,新位置為偏移量的大小;
SEEK_CUR:當前位置為文件指針的位置,新位置為當前位置加上偏移量。
SEEK_END:當前位置為文件的結尾,新位置為文件的大小加上偏移量的大小。函
數的
返回值:成功:文件當前的位置,出錯:-1。
5.close(fd),
調用close()函數可以關閉一個打開的文件。
調用成功返回0,出錯返回-1,并設置errno;
注:當一個進程終止時,該進程打開的所有文件都由內核自動關閉;
上面程序就可以讀到hello linux
如果lseek這么使用的話那么會讀到:llo linux
例子:拷貝文件==》cp命令的實現
2.標準IO
2.1與文件IO的區別?
文件IO:是直接調用內核提供的系統調用函數, 頭文件是unistd.h
標準IO:是間接調用系統調用函數,頭文件是: stdio.h
printf是間接調用,write是直接調用,1這個文件描述符就代碼著標準輸出
之前學過:輸入輸入相關的函數,都是和標準的輸入(鍵盤),標準的輸出(顯示器)
getchar(),putchar() ----一個字符
gets(buf),puts(buf) ----一串字符
scanf(),printf() ---- 一個字符,一串字符都可以
與一些普通文件的讀寫沒有關系,也即這些函數不能讀寫普通文件。
標準IO中的相關函數,不僅可以讀寫普通文件,也可以向標準的輸入或標準的輸出中讀或寫。
三個緩存的概念(數組):
1.我們的程序中的緩存,就是你想從內核讀寫的緩存(數組)----用戶空間的緩存
2.每打開一個文件,內核在內核空間中也會開辟一塊緩存,這個叫內核空間的緩存
文件IO中的寫即是將用戶空間中的緩存寫到內核空間的緩存中。
文件IO中的讀即是將內核空間的緩存寫到用戶空間中的緩存中。
3.標準IO的庫函數中也有一個緩存,這個緩存稱為----庫緩存
例1:測試驗證庫緩存的存在
標準IO庫函數在什么時候會調用系統調用函數?
標準IO庫緩存寫滿或滿足一定條件時,會調用系統調用函數。
printf滿足一定條件 : 遇到\n 時,即會將庫緩存的內容寫到內核中,即調用了系統調用函數。( printf對應行緩存1024個字節)
#include "stdio.h" int main() {char buf[]="hello linux\n";printf("%s",buf);while(1);return 0; }庫緩存寫滿時,會調用系統調用函數,將lib_buffer 內容寫到kernel_buffer中去
#include "stdio.h" int main() {char buf[]="hello linux";int i=0;while(i< 93){printf("%s",buf);i++;}printf("h");while(1);return 0; }文件IO: 標準IO
open fopen
close fclose
lseek fseek, rewind
read 讀寫函數比較多(分三類,全緩存、行緩存和無緩存)
write
FILE *fopen (const char *path, const char *mode);
返回值:FILE * 文件流指針 類似于文件IO 中的文件描述符
FILE 定義:struct _IO_FILE,在/usr/include/libio.h
包含讀寫緩存的首地址、大小、位置指針等。
標準的輸入流:stdin 0
標準的輸出流:stdout 1
標準的出錯流:stderr 2
文件IO flag
r或rb 打開只讀文件,該文件必須存在。
r+或r+b 打開可讀寫的文件,該文件必須存在。
w或wb 打開只寫文件,若文件存在則文件長度清為0,即會擦些文件以
前內容。若文件不存在則建立該文件。
w+或w+b或wb+ 打開可讀寫文件,若文件存在則文件長度清為零,即會擦些文件
以前內容。若文件不存在則建立該文件。
a或ab 以附加的方式打開只寫文件。若文件不存在,則會建立該文件,
如果文件存在,寫入的數據會被加到文件尾,即文件原先的內容
會被保留。
a+或a+b或ab+ 以附加方式打開可讀寫的文件。若文件不存在,則會建立該文
件,如果文件存在,寫入的數據會被加到文件尾后,即文件原先
的內容會被保留。
?b:二進制文件
?r: 只讀方式打開文件,文件必須存在;
?w或a:只寫方式打開文件,文件不存在則創建;
區別: w等價O_TRUNC,a等價O_APPEND;
?+:讀寫方式打開文件,文件必須存在;
例:以讀寫方式打開一個文件,該文件必須存在: r+
以追加方式打開一個文件,若文件不存在,則創建: a或a+
fopen最終生成文件的權限都是0666&(~umask)
int fclose(FILE *stream)
?fclose()調用成功返回0,失敗返回EOF,并設置errno
?在該文件被關閉之前,刷新緩存中的數據。如果標準I / O庫已經為該流自動分配了一個緩存,則釋放此緩存。
讀寫函數:
三類讀寫函數:
一類**:行緩存** 遇到新行符(\n) 或寫滿緩存時,即調用系統調用函數
讀:fgets, gets, printf, fprintf,sprintf
寫:fputs, puts,scanf
一個字符的讀寫,是否是行緩存?
讀:fgetc, getc, getchar
寫:fputc, putc,putchar
二類:無緩存 只要用戶調這個函數,就會將其內容寫到內核中
三類:全緩存 只有寫滿緩存再調用系統調用函數
讀:fread
寫:fwrite
行緩存的讀寫函數fgets和fputs
char *fgets (char *s, int size, FILE *stream)
第一個參數:緩存,即讀到哪里去
第二個參數:讀多少個字節
第三個參數:從什么地方讀
返回值若成功則為s(緩存的地址),若已處文件尾端或出錯則為null
int fputs(const char *s,FILE *stream);
第一個參數:緩存,即寫什么內容
第二個參數:寫到哪里去
若成功則為非負值,若出錯則為EOF -1 。
同樣的下面這個文件是不能讀出數據的(需要調整文件位置指針)
刷新緩存函數:fflush(FILE *fp)
把庫函數中的緩存的內容強制寫到內核中。
fclose()函數里邊包含fflush();
無緩存,行緩存,全緩存
stderr 無緩存
stdout 行緩存
下面程序是寫入標準輸出,由于標準輸出是行緩存,所以下面程序沒有結束時不會寫入到內核當中的
但是下面程序在執行的時候是可以把數據寫進文件的,所以可以看出stderr是無緩存的
調整讀寫位置指針函數:
fseek() 參數與lseek是一樣的但是返回值不一樣
lseek的返回值是:當前文件的位置指針值;
fseek()的返回值是:成功返回0,失敗返回-11;
rewind(FILE *fp) 用于設定流的文件位置指示為文件開始,該函數調用成功無返回值。
rewind()等價于(void)fseek(fp 0, SEEK_SET);
ftell(FILE *fp)
用于取得當前的文件位置,調用成功則為當前文件位置指示,若出錯則為-1L;
行緩存的讀寫函數gets和puts
char *gets(char *s);
int puts(const char *s);
gets 與fgets的區別:
?gets()時不能指定緩存的長度,這樣就可能造成緩存越界(如若該行長于緩存長度),寫到緩存之后的存儲空間中,從而產生不可預料的后果;
?gets()只能從標準輸入中讀,fgets()還可以從普通文件里邊讀;
?gets()與fgets()的另一個區別是: gets()并不將新行符存入緩存中, fgets 將新行符存入緩存中(新行符就是回車);
puts 與fputs的區別:
?puts()只能向標準輸出中寫;
?puts()與fputs()的另一個區別是: puts 輸出時會添加一個新行符, fputs不會添加;
fprintf、printf、sprintf 行緩存的函數
int fprintf(FILE *stream,”字符串格式”) ;
fprintf可以輸出到文件中,也可輸出到顯示器,
printf 只能輸出到顯示器中。
int sprintf(str *, “字符串格式”)
輸出內容到一個字符串中
一個字符讀寫函數fgetc和fputc
int fgetc(FILE *fp)
功能:從文件中讀取一個字符; 不僅僅可以從標準輸入上讀,還可以從普通文件上讀
參數:文件流
返回值:正確為讀取的字符,到文件結尾或出錯時返回EOF。
int fputc(int c, FILE *fp)
功能:寫一個字符到文件中
參數:第一個參數為要寫的字符,第二個參數為文件流
返回值:成功則返回輸入的字符,出錯返回EOF。
fputc有緩存,但不是行緩存函數所以加上‘\n’也不會把緩沖數據刷新到內核中,所以下面例子是不能把數據寫到a.c中去的。只有加上flussh就能把數據刷新到內核中
fgets的使用:
char *fgets (char *s, int size, FILE *stream)
返回值若成功則為s(緩存的地址),若已處文件結尾或讀錯則為null
int fgetc(FILE *fp)
返回值:正確為讀取的字符,到文件結尾或讀錯時返回EOF。-1
當返回錯誤時,怎樣判讀是已經到達文件結尾(fgets與fgetc),還是讀錯呢?
3.8int feof(FILE *stream);
功能:判斷是否已經到文件結束
參數:文件流
返回值:到文件結束,返回為非0,沒有則返回0
3.9 int ferror(FILE *stream);
功能:判斷是否讀寫錯誤
參數:文件流
返回值:是讀寫錯誤,返回為非0,不是則返回0
3.10void clearerr(FILE *stream);
功能:清除流錯誤
參數:文件流
下面程序ferror始終為0(如果到文件結尾還讀的話那么ferror的值為非0)所以不是讀寫錯誤,而是流錯誤
例 cat 命令的實現
全緩存的二個函數:
全緩存的函數是只要把庫緩存寫滿才會把庫緩存內容寫進內核當中的。size和nmemb的區別,首先比如要寫進int arr[100],那么size是4,nmemb是100
上面程序沒有輸出,是文件指針問題
fgetc與fputc與read,write等四組效率對比(拷貝同一個文件):
fread>fgets>fgetc>read
3Linux下靜態庫和動態庫(共享庫)的制作與使用
Linux操作系統支持的函數庫分為:
?靜態庫,libxxx.a,在編譯時就將庫編譯進可執行程序中。
優點:程序的運行環境中不需要外部的函數庫。
缺點:可執行程序大(因為運行的時候已經導入函數)
?動態庫,又稱共享庫,libxxx.so,在運行時將庫加載到可執行程序中。
優點:可執行程序小。
缺點:程序的運行環境中必須提供相應的庫。
函數庫目錄:/lib /usr/lib。
靜態庫的制作:
1.生成目標文件:gcc -c file.c
2.靜態函數庫創建命令ar
ar -cr libfile.a file.o
-c: create的意思
-r: replace的意思,表示當插入的模塊file.o已經存在libfile.a中,則覆蓋。反之ar顯示一個錯誤消息。
操作靜態庫的幾個實例:
情況1: 如果從別處得到一個靜態庫libunknown.a,想知道其中包含哪些模塊。
命令:ar -t libunknown.a
靜態庫的編譯:gcc -o main main.c -L. -lfile編譯main.c就會把靜態函數庫整合進main。
其中:
-L指定靜態函數庫的位置供查找,注意L后面還有’.’,表示靜態函數庫在本目錄下查找。
-l則指定了靜態函數庫名,由于靜態函數庫的命名方式是lib***.a,其中的lib和.a忽略。
刪除libaddsub.a后main依然可以運行,因為靜態庫的內容已經整合進去了。
動態函數庫的制作:
1.生成目標文件:gcc -c file.c
2. gcc -shared -fpic -o libfile.so file.o
-fpic:聲明產生位置無關代碼。
-shared:生成共享庫。
用上述命令生成libaddsub.so 動態函數庫。
gcc -o out main.c -L. -lfile
此時還不能立即./out,因為在動態函數庫使用時,會查找/usr/lib /lib目錄下的動態函數庫,而此時我們生成的庫不在里邊。
第一種方法:
libaddsub.so放到/usr/lib 或/lib中去。(mv)
第二種方法,假設libfile.so在/home/linux/file 環境變量方法
export LD_LIBRARY_PATH=/home/linux/addsub:$LD_LIBRARY_PATH
第三種方法:
在/etc/ld.so.conf文件里加入我們生成的庫的目錄,然后/sbin/ldconfig。
/etc/ld.so.conf是非常重要的一個目錄,里面存放的是鏈接器和加載器搜索共享庫時要檢查的目錄,默認是從/usr/lib /lib中讀取的,所以想要順利運行,可以把我們庫的目錄加入到這個文件中并執行/sbin/ldconfig。
4.目錄IO
#include <sys/types.h>
#include <dirent.h>
目錄I/O與文件I/O函數的比較
目錄I/O 文件I/O
opendir 只能打開目錄
mkdir 創建目錄 open
readdir 讀目錄 read
rewinddir 調整位置指針
telldir
seekdir rewind
ftell
fseek
closedir:關閉目錄 close
1.opendir
DIR *opendir(const char *pathname) ;
參數:打開的目錄以及路徑
返回值:成功返回目錄流指針,出錯返回NULL。
int mkdir(const char * path, mode_t mode)
path為欲創建的目錄文件路徑,
mode為該目錄的訪問權限
返回值:若目錄創建成功,則返回0;否則返回-1
生成的目錄權限仍和umask有關系。
2.readdir
struct dirent *readdir(DIR *dr);
參數:目錄流指針
返回值:成功則為struct dirent指針,若在目錄尾或出錯則返回NULL。
struct dirent定義在頭文件dirent.h中。
此結構至少包含下列兩個成員:
struct dirent
{
ino_t d_ino; // inode 號
char d_name[NAME_MAX+1]; //文件名
}
按照上圖只能讀一個目錄,因為目錄里邊是按照鏈表進行存儲,使用一個死循環就可以把目錄里的所有文件都輸出出來
3.rewinddir:重置讀取目錄流的位置為開頭
void rewinddir(DIR *dr);
參數:目錄流指針
long telldir(DIR *dirp)
參數:目錄流指針
返回值:目錄流當前位置
void seekdir(DIR *dirp , long loc)
類似于文件定位函數fseek(),在目錄流上設置下一個readdir()操作的位置。
參數:目錄流指針和偏移量
int close(DIR *dr);
參數:目錄流指針
返回值:成功返回 0,出錯返回- 1。
總結
以上是生活随笔為你收集整理的Linux高级篇——IO系统编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LInux命令行参数
- 下一篇: 机器的速度与主频之间的关系