知識儲備:
C庫I/O:點擊打開鏈接
Linux系統I/O:
文件描述符:
每個進程在Linux內核中都有一個task_struct結構體來維護進程相關的信息,稱爲進程描述符(Process Descriptor),而在操作系統理論中稱爲進程控制塊(PCB,Process Control Block)。task_struct中有一個指針指向files_struct結構體,稱爲文件描述符表,其中每個表項包含一個指向已打開的文件的指針,用戶程序不能直接訪問內核中的文件描述符表,而只能使用文件描述符表的索引(即0、1、2、3這些數字),這些索引就稱爲文件描述符(File Descriptor)。當調用open打開一個文件或創建一個新文件時,內核分配一個文件描述符並返回給用戶程序,該文件描述符表項中的指針指向新打開的文件。當讀寫文件時,用戶程序把文件描述符傳給read或write,內核根據文件描述符找到相應的表項,再通過表項中的指針找到相應的文件。0,1,2系統自動打開,分別代表標準輸入,標準輸出,和標準錯誤。
系統最多能打開多少個文件描述符,受內存影響,可用: cat/proc/sys/fs/file_max 命令查看
1.int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:打開或者創建一個文件
返回值:成功返回文件描述符。出錯返回-1
參數:
1.pathname參數是要打開或創建的文件名,和fopen一樣,pathname既可以是相對路徑也可以是絕對路徑。
2.flags參數有一系列常數值可供選擇,可以同時選擇多個常數用按位或運算符連接起來,所以這些常數的宏定義都以O_開頭,表示or。
必選項:以下三個常數中必須指定一個,且僅允許指定一個。
O_RDONLY 只讀打開
O_WRONLY 只寫打開
O_RDWR 可讀可寫打開
以下可選項可以同時指定0個或多個,和必選項按位或起來作爲flags參數。可選項有很多,這裏只介紹一部分,其它選項可參考open(2)的Man Page:
O_APPEND 表示追加。如果文件已有內容,這次打開文件所寫的數據附加到文件的末尾而不覆蓋原來的內容。
O_CREAT 若此文件不存在則創建它。使用此選項時需要提供第三個參數mode,表示該文件的訪問權限。
O_EXCL 如果同時指定了O_CREAT,並且文件已存在,則出錯返回。
O_TRUNC 如果文件已存在,並且以只寫或可讀可寫方式打開,則將其長度截斷(Truncate)爲0字節。
3.mode指定文件權限,可以用八進制數表示,比如0644,詳見open(2)的Man Page。要注意的是,文件權限由open的mode參數和當前進程的umask掩碼共同決定。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int op = open("hehe.txt", O_RDWR);
if(-1 == op){
perror("open");
exit(1);
}
while(1)
{
pause();
}
return 0;
}
2.int close(int fd);
功能:關閉一個已經打開的文件
返回值:成功返回0,出錯返回-1並且設置errno
參數fd即是要關閉的文件描述符。
補充:由open返回的文件描述符一定是該進程尚未使用的最小描述符。由於程序啓動時自動打開文件描述符0、1、2,因此第一次調用open打開文件通常會返回描述符3,再調用open就會返回4。可以利用這一點在標準輸入、標準輸出或標準錯誤輸出上打開一個新文件,實現重定向的功能。例如,首先調用close關閉文件描述符1,然後調用open打開一個常規文件,則一定會返回文件描述符1,這時候標準輸出就不再是終端,而是一個常規文件了,再調用printf就不會打印到屏幕上,而是寫到這個文件中了。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
close(1);
int fd = open("closefile", O_RDWR|O_CREAT, 0644);
if(-1 == fd){
perror("open");
exit(1);
}
char *msg = "abcdefghijklmnopqrstuvwxyz";
write(fd, msg, strlen(msg));
//1被關閉,無法打印到屏幕,而是寫入到文件當中
printf("fd = %d\n", fd);
printf("this is czf!");
close(fd);
return 0;
}
3. ssize_t read(int fd, void *buf, size_t count);
功能:從打開的設備或文件中讀取數據
返回值:成功返回讀取的字節數,出錯返回-1並設置errno,如果在調read之前已到達文件末尾,則這次read返回0
參數:參數count是請求讀取的字節數,讀上來的數據保存在緩衝區buf中,同時文件的當前讀寫位置向後移。count小於buf_size時,將fd內容整體複製。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h> // ./a.out aaa bbb 把aaa中的內容讀到buf再寫入到bbb
int main(int argc, char *argv[])
{
if(3 != argc)
{
fprintf(stderr,"usage:%s from to\n", argv[0]);//輸入錯誤 -- 輸入格式 ./a.out aaa bbb
exit(0);
}
int fd_in = open(argv[1],O_RDONLY);
if(-1 == fd_in){
perror("open");
exit(1);
}
int fd_out = open(argv[2], O_WRONLY|O_CREAT,0644);//只寫方式打開
if(-1 == fd_out){
perror("open");
close(fd_in);
exit(1);
}
char buf[10];
while(1){
memset(buf, 0x00, sizeof(buf));
int r = read(fd_in, buf, 10);
if(r <= 0) break;
write(fd_out, buf, r);
write(1, buf, r);
}
close(fd_in);
close(fd_out);
return 0;
}
4. ssize_t write(int fd, const void *buf, size_t count);
功能:向打開的設備或文件中寫數據
返回值:成功返回寫入的字節數,出錯返回-1並設置errno
參數:同read
5. off_t lseek(int fd, off_t offset, int whence);
功能:lseek和標準I/O庫的fseek函數類似,可以移動當前讀寫位置
返回值:lseek成功時返回當前偏移量失敗時返回-1
參數:同fseek offset可正可負 whence 可以設置SEEK_SET, SEEK_CUR, SEEK_END
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h> //從文件開始每隔一個字符讀一個
int main()
{
int fd = open("lseekfile",O_RDWR|O_CREAT, 0644);
if(-1 == fd){
perror("open");
exit(1);
}
char *msg = "abcdefghjklimnopqrstuvwxyz";
write(fd, msg, strlen(msg));
lseek(fd, 0, SEEK_SET);
char c;
while(1){
if(read(fd, &c, 1) <= 0) break;
// write(1, &c, 1);
printf("%c", c);
lseek(fd, 1, SEEK_CUR);
}
close(fd);
return 0;
}
6.int truncate(int fd, off_t length);
功能:文件截斷 (佔位置)
返回值:成功返回0, 失敗返回-1
參數:lenth佔位長度
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h> //截斷一個內存大小爲1K的文件
int main()
{
int op = open("FileOfTruncate", O_RDWR | O_CREAT, 0644);
if(-1 == op){
perror("open");
exit(1);
}
ftruncate(op, 1024); printf("create ok!\n");
write(op, "aaaaaa", 2);
return 0;
}
7.int dup(int oldfd)文件描述符的複製,從最小找第一個沒有被使用過的描述符來使用
int dup2(int oldfd, int newfd);
功能:複製一個現存的文件描述符
返回值:成功返回新的文件描述符,出錯返回-1
#include <stdio.h> //close.c
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
close(1);
int fd = open("closefile", O_RDWR|O_CREAT, 0644);
if(-1 == fd){
perror("open");
exit(1);
}
char *msg = "abcdefghijklmnopqrstuvwxyz";
write(fd, msg, strlen(msg));
printf("fd = %d\n", fd);//1被關閉,無法打印到屏幕
printf("this is czf!");
close(fd);
return 0;
}
#include <stdio.h> //dup-close.c
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd = open("dupfile", O_RDWR|O_CREAT, 0644);
if(-1 == fd){
perror("open");
exit(1);
}
char *msg = "abcdefghijklmnopqrstuvwxyz";
write(fd, msg, strlen(msg));
fprintf(stderr, "fd = %d\n", fd);
close(1);
dup(fd); //文件描述複製
printf("after fd = %d\n ", fd); //1已經被關閉,
//本來應該輸出到屏幕的,最終打印到文件當中
printf("this is czf!");
close(fd);
return 0;
}
文件共享:
父進程的文件描述符都被複制到子進程中。父子進程打開相同的文件表項。這樣的共享方式,讓父子進程對同一文件使用了一個文件偏移量。如果父子進程寫到同一描述符文件,但又沒有任何形式的同步(父進程等待子進程),那麼它們的輸出就會相互混合(假定所用的描述符時fork之前打開的)。父進程等待子進程完成。在這種情況下,父進程無需對其描述符做任何處理。當子進程終止後,它曾進行過讀寫的任一共享描述符的文件偏移量一致行了相應的更新。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd = open("forkfile",O_RDWR);
if(-1 == fd) perror("open"),exit(1);
printf("before fd = %d\n",fd);
pid_t pid = fork();
if(-1 == pid){
perror("fork");
exit(1);
}
else if (pid > 0){
write(fd, "ABCDEFGH", 8);//父進程寫入
close(fd); //關閉(引用計數減一),如果子進程能讀出來,
//說明用的是同一個文件表,文件表保存了(打開標誌,當前讀寫位置,引用計數,和V節點指針等)
printf("parent fd = %d\n",fd);
printf("parent finish!\n");
}
else{
sleep(1);
char buf[3] = {};
// lseek(fd, 0, SEEK_SET);
if(read(fd, buf, 2) == -1) perror("read");
printf("buf = %s\n", buf);
printf("child fd = %d\n",fd);
printf("child finish\n");
// write(fd, "aaaaa",5);
close(fd);
}
return 0;
}