基礎IO
- 1.C語言操作檔案介面(回顧)
- 1.1 FILE *fopen(const char *path,const char *mode)
- 1.2 size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream)
- 1.3 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
- 1.4 int fseek(FILE *stream, long offset, int whence)
- 1.5 int fclose(FILE *stream)
- 2. 系統檔案IO
- 2.1 int open(const char *pathname, int flags, mode_t mode)
- 2.2 ssize_t write(int fd, const void *buf, size_t count)
- 2.3 ssize_t read(int fd, void *buf, size_t count)
- 2.4 off_t lseek(int fd, off_t offset, int whence)
- 2.5 int close(int fd)
- 3. 檔案描述符
- 0&1&2
- 檔案描述符的分配規則
- 檔案描述符與檔案流指標的區別
- 檔案描述符(檔案句柄)泄露的問題
- 一個行程打開檔案的最大數量
- 4. 重定向
- 命令列感受
- 重定向介面 int dup2(int oldfd,int newfd)
1.C語言操作檔案介面(回顧)
fopen,fwrite,fread,fseek,fclose這些函式都是庫函式,是c庫當中提供能給程式員呼叫的函式,
1.1 FILE *fopen(const char *path,const char *mode)
函式描述:
path:待打開的檔案(檔案路徑+檔案名稱)
mode:以何種方式打開
- r —— 以只讀方式打開,當檔案不存在的時候,就會打開失敗
- r+ —— 以讀寫方式打開,當檔案不存在的時候,就會打開失敗
- w —— 以只寫方式打開,如果檔案不存在,則創建檔案,如果檔案存在,則會截斷(清空)檔案
- w+ —— 以讀寫方式打開,如果檔案不存在,則創建檔案,如果檔案存在,則會截斷(清空)檔案
- a —— 以追加方式打開,只支持寫,如果檔案不存在,則創建檔案,當前的檔案流指標指向了檔案的末尾
- a+ —— 以追加方式打開,支持讀和寫,如果檔案不存在,則創建檔案,當前的檔案流指標指向了檔案的末尾
回傳值:打開成功回傳檔案流指標,打開失敗回傳NULL,
示例:
- 示例1:以 r,r+ 方式打開
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
//以只讀方式打開檔案linux
FILE *fp = fopen("./linux","r"); // 不管是r還是r+,如果檔案不存在都會打開失敗
if(!fp)
{
perror("fopen");
return -1;
}
printf("open success\n");
return 0;
}
//輸出結果:
fopen: No such file or directory
創建linux檔案,再次以只讀方式打開,結果如下:
[test@localhost c_file]$ touch linux
[test@localhost c_file]$ ./test_file
open success
- 示例2:以 w,w+方式打開
首先刪掉前面創建的linux檔案,然后以只寫方式打開:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
FILE *fp = fopen("./linux","w"); //不管是w還是w+,如果檔案不存在都會創建檔案
//如果存在,則清空檔案
if(!fp)
{
perror("fopen");
return -1;
}
printf("open success\n");
return 0;
}
//輸出結果:
open success
此時查看檔案:
[test@localhost c_file]$ make
gcc test.c -o test_file
[test@localhost c_file]$ ./test_file
open success
[test@localhost c_file]$ ls
linux makefile test.c test_file
現在linux檔案存在,往這個檔案中隨便寫入資料,然后再次以只寫方式打開檔案:
[test@localhost c_file]$ cat linux
Hello World
[test@localhost c_file]$ ./test_file
open success
[test@localhost c_file]$ cat linux
[test@localhost c_file]$ #檔案內容已經被清空
- 示例3:以a,a+方式打開
首先刪掉前面創建的linux檔案,然后以追加方式打開:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
FILE *fp = fopen("./linux","a"); //不管是a還是a+,如果檔案不存在都會創建檔案
//如果存在,當前檔案流指標指向檔案末尾,并不會清空檔案內容
if(!fp)
{
perror("fopen");
return -1;
}
printf("open success\n");
return 0;
}
//輸出結果:
open success
現在linux檔案存在,往這個檔案中隨便寫入資料,然后再次以追加方式打開檔案:
[test@localhost c_file]$ echo "Hello world" >> linux
[test@localhost c_file]$ cat linux
Hello world
[test@localhost c_file]$ ./test_file
open success
[test@localhost c_file]$ cat linux
Hello world #可以看到檔案內容沒有被清空
1.2 size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream)
函式描述:
ptr:要往檔案當中寫的內容
size:寫入塊的大小,單位是位元組
nmemb:塊的個數,單位是個(寫入檔案位元組的數量:size * nmemb)
注意:一般在程式中使用時,是將size設定為1,則nmemb就表示寫入的位元組數量,
stream:檔案流指標
回傳值:回傳寫入成功塊的個數,切記不是寫入成功位元組的數量,
示例:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main()
{
FILE *fp = fopen("./linux","w+"); //以讀寫方式打開檔案
if(!fp)
{
perror("fopen");
return -1;
}
printf("open success\n");
//創建buf陣列用于保存需要往檔案寫入的資料
char buf[1024] = {0};
const char *ptr = "Hello World!";
strncpy(buf,ptr,strlen(ptr));
size_t ret = fwrite(buf,1,strlen(ptr),fp);
printf("ret:%d\n",ret); // 回傳的是寫入成功塊的個數
return 0;
}
輸出結果:
[test@localhost c_file]$ ./test_file
open success
ret:12
[test@localhost c_file]$ cat linux
Hello World![test@localhost c_file]$
1.3 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
函式描述:
ptr:要將讀到的內容保存在哪里
size:每次讀塊的大小
nmemb:塊的個數
stream:檔案流指標
回傳值:成功讀到的塊的個數,回傳0說明讀取成功了但是沒有讀到內容,
示例:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main()
{
FILE *fp = fopen("./linux","r");
if(!fp)
{
perror("fopen");
return -1;
}
printf("open success\n");
// 創建陣列用以保存讀取的資料
char buf[1024] = {0};
size_t ret = fread(buf,1,sizeof(buf)-1,fp);
printf("buf:%s\n",buf);
printf("ret:%d\n",ret); // 回傳的是讀到的塊的個數
return 0;
}
輸出結果:
[test@localhost c_file]$ ./test_file
open success
buf:Hello world
ret:12
1.4 int fseek(FILE *stream, long offset, int whence)
函式描述:
stream:檔案流指標
offset:偏移量
whence:
- SEEK_SET:檔案流指標偏移到檔案頭部
- SEEK_CUR:檔案流指標偏移到當前位置
- SEEK_END:檔案流指標指向檔案末尾位置
示例:
在一個程式中在檔案寫入資料后,想要繼續讀取資料,此時就用到了fseek函式
不使用fseek:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main()
{
FILE *fp = fopen("./linux","w+");
if(!fp)
{
perror("fopen");
return -1;
}
printf("open success\n");
char buf_w[1024] = {0};
const char *ptr = "Hello World!";
strncpy(buf_w,ptr,strlen(ptr));
size_t ret_w = fwrite(buf_w,1,strlen(ptr),fp);
printf("ret_w:%d\n",ret_w);
//fseek(fp,0,SEEK_SET); //將檔案流指標偏亮到檔案頭,偏移量為0
char buf_r[1024] = {0};
size_t ret_r = fread(buf_r,1,sizeof(buf_r)-1,fp);
printf("buf_r:%s\n",buf_r);
printf("ret_r:%d\n",ret_r);
return 0;
}
輸出結果:
[test@localhost c_file]$ ./test_file
open success
ret_w:12
buf_r: #此時檔案流指標指在檔案末尾,所以沒有讀取到
ret_r:0 # 回傳值為-1,表示fread函式呼叫錯誤,為0,則表示讀取成功了,但是沒有讀到內容
使用fseek后:
[test@localhost c_file]$ ./test_file
open success
ret_w:12
buf_r:Hello World!
ret_r:12
[test@localhost c_file]$ cat linux
Hello World![test@localhost c_file]$
1.5 int fclose(FILE *stream)
關閉檔案流指標
fclose(fp); // fp 檔案流指標
2. 系統檔案IO
—— 系統呼叫函式的操作檔案介面
open,write,read,lseek,close這些函式都是系統呼叫,是作業系統內核為程式員提供的函式
2.1 int open(const char *pathname, int flags, mode_t mode)
函式描述:
pathname:要打開的檔案名稱(路徑+名稱)
flags:以何種方式打開
-
必須的宏,三個宏有且只能出現一個
- O_RDONLY —— 只讀方式
- O_WRONLY —— 只寫方式
- O_RDWR —— 讀寫方式
-
可選的宏
- O_APPEND —— 追加
- O_TRUNC —— 截斷
- O_CREAT —— 檔案不存在則創建
使用方式:必須的宏和可選的宏之間使用按位或的方式(部分)
- O_RDONLY —— 八進制的0
- O_WRONLY —— 八進制的1
- O_RDWR —— 八進制的2
- O_CREAT —— 八進制的100
例:O_RDWR | O_CREAT (是按照位圖的方式來使用的)

mode:權限,給新創建出來的檔案設定權限,傳參的時候,傳八進制數字就可以了,
回傳值:打開成功,回傳大于等于0的數字,是檔案描述符,打開失敗,回傳-1,
示例:
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd = open("./linux",O_RDWR | O_CREAT,0664);
if(fd<0)
{
perror("open");
return -1;
}
printf("open success\n");
printf("fd:%d\n",fd);
return 0;
}
輸出結果:
[test@localhost sys_file]$ ./sys_file
open success
fd:3
[test@localhost sys_file]$ ls
linux makefile sys_file test.c
2.2 ssize_t write(int fd, const void *buf, size_t count)
函式描述:
fd:檔案描述符,open的回傳值
buf:往檔案里寫的內容
count:寫的內容的大小
回傳值:寫成功的位元組數量
示例:
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd = open("./linux",O_RDWR | O_CREAT,0664);//以讀寫方式打開檔案,如果檔案不存在,則創建
if(fd<0)
{
perror("open");
return -1;
}
printf("open success\n");
printf("fd:%d\n",fd);
char buf[1024] = {0};
const char *ptr = "Hello World!";
strncpy(buf,ptr,strlen(ptr));
write(fd,buf,strlen(ptr));
return 0;
}
輸出結果:
[test@localhost sys_file]$ ./sys_file
open success
fd:3
[test@localhost sys_file]$ cat linux
Hello World![test@localhost sys_file]$
2.3 ssize_t read(int fd, void *buf, size_t count)
函式描述:
fd:檔案描述符,open的回傳值
buf:要將讀到的內容放到哪里去
count:最大可以讀多少個單位位元組
回傳值:回傳讀到的位元組數量,回傳0說明讀取成功了,但是沒有讀取到內容
示例:
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd = open("./linux",O_RDWR | O_CREAT,0664);
if(fd<0)
{
perror("open");
return -1;
}
printf("open success\n");
printf("fd:%d\n",fd);
char buf[1024] = {0};
read(fd,buf,sizeof(buf)-1); //-1 是為了給 '\0'留一個位置
printf("buf:%s\n",buf);
return 0;
}
輸出結果:
[test@localhost sys_file]$ ./sys_file
open success
fd:3
buf:Hello World!
2.4 off_t lseek(int fd, off_t offset, int whence)
函式描述:
fd:檔案描述符
offset:偏移量
whence:
- SEEK_SET:檔案流指標偏移到檔案頭部
- SEEK_CUR:檔案流指標偏移到當前位置
- SEEK_END:檔案流指標指向檔案末尾位置
示例:
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
int main()
{
//打開檔案
int fd = open("./linux",O_RDWR | O_CREAT,0664);
if(fd<0)
{
perror("open");
return -1;
}
printf("open success\n");
printf("fd:%d\n",fd);
//寫
char buf_w[1024] = {0};
const char *ptr = "Hello World!";
strncpy(buf_w,ptr,strlen(ptr));
write(fd,buf_w,strlen(ptr));
//lseek(fd,0,SEEK_SET); //將檔案流指標偏移到檔案頭
//讀
char buf_r[1024] = {0};
read(fd,buf_r,sizeof(buf_r)-1); //-1 是為了給 '\0'留一個位置
printf("buf_r:%s\n",buf_r);
return 0;
}
不使用lseek輸出結果:
[test@localhost sys_file]$ ./sys_file
open success
fd:3
buf_r: #此時檔案流指標指在檔案末尾,所以沒有讀取到
使用lseek之后:
[test@localhost sys_file]$ ./sys_file
open success
fd:3
buf_r:Hello World!
2.5 int close(int fd)
關閉檔案描述符
close(fd); //fd 檔案描述符
close(0); // 關閉標準輸入
close(1); // 關閉標準輸出
close(2); // 關閉標準錯誤
3. 檔案描述符
通過對open函式的學習與理解,檔案描述符fd就是一個整數,
0&1&2
作業系統會為每一個行程在磁盤上創建一個以行程號命名的檔案夾,在該檔案夾下有一個fd檔案夾,保存的資訊即為該行程打開的檔案描述符資訊,
下面來一段代碼演示:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
int fd = open("./linux",O_RDWR | O_CREAT,0644);//打開檔案
if(fd<0)
{
perror("open");
return -1;
}
printf("fd:%d\n",fd);
while(1)
{
sleep(1);
}
return 0;
}
//輸出結果
fd:3 //然后程式死回圈,方便查看資訊
查看該行程的檔案描述符資訊:

可以看到,當我們新創建出來一個行程,勢必會打開3個檔案描述符,分別對應,標準輸入 (0),標準輸出 (1),標準錯誤 (2),
如圖所示,當 ./main運行該程式時:

可以看到,檔案描述符其實就是在內核當中的fd_array陣列的下標,
檔案描述符的分配規則
通過上面的代碼發現,打開新的檔案后,fd的值為3,那么我們關閉0或者2再看
示例代碼:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
close(0);
int fd = open("./linux",O_RDWR | O_CREAT,0644);//打開檔案
if(fd<0)
{
perror("open");
return -1;
}
printf("fd:%d\n",fd);
while(1)
{
sleep(1);
}
return 0;
}
//輸出結果:
fd:0 //然后程式死回圈,方便查看資訊
如圖所示:關閉0以后

如圖所示:關閉2以后

結論:檔案描述符的分配規則:在files_struct陣列當中,找到當前沒有被使用的最小的一個下標,作為新的檔案描述符即最小未占用原則,
檔案描述符與檔案流指標的區別
如圖所示:

區別:
- 檔案流指標是fopen函式回傳的,檔案流指標是屬于c庫在維護的
- 檔案描述符是open函式回傳的,檔案描述符是內核在維護的,
- 不同的檔案流指標,在c庫當中會創建不同的struct _IO_FILE,在_IO_FILE 結構體當中保存了不同的檔案描述符,
- 檔案流指標當中包含檔案描述符
- 作業系統廣泛存在緩沖區,但是如果針對檔案流指標而言的緩沖區,是c庫在維護的,
- exit函式在退出行程時,會重繪緩沖區,就是因為操作的是檔案流指標
- _exit函式在退出行程時,不會重繪該緩沖區,是因為該緩沖區是c庫在維護的,內核并不知道,所以不會重繪,
檔案描述符(檔案句柄)泄露的問題
當我們打開一個檔案,作業系統會給程式分配一個檔案描述符,如果在使用完畢之后,沒有及時關閉檔案,就會造成檔案句柄泄露,
一個行程打開檔案的最大數量
-
命令列查看:

可以使用ulimit -[選項][值] 修改對應內容,比如打開檔案最大數量, -
代碼查看:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
int fd_count=0;
while(1)
{
int fd = open("./linux",O_RDWR | O_CREAT,0664); // 一直打開檔案
if(fd<0)
{
perror("open");
break;
}
printf("fd:%d\n",fd);
fd_count++;
}
printf("fd_count:%d\n",fd_count);
return 0;
}
輸出結果:
[test@localhost fd]$ ./fd_count
fd:3
fd:4
fd:5
fd:6
fd:7
……
fd:1019
fd:1020
fd:1021
fd:1022
fd:1023
open: Too many open files #到1024就停下來了
fd_count:1021 #從檔案描述符3開始到1023
[test@localhost fd]$
由結果可得知:fd從3開始是因為新創建出來一個行程,作業系統勢必會打開3個檔案描述符,即 0(標準輸入)、1(標準輸出)、2(標準錯誤)
4. 重定向
命令列感受
- 清空重定向
[test@localhost dup]$ echo "Hello" > linux #將Hello重定向到linux檔案中
[test@localhost dup]$ cat linux
Hello
[test@localhost dup]$ echo "linux" > linux #清空linux檔案內容,再把linux重定向到linux檔案中
[test@localhost dup]$ cat linux
linux
[test@localhost dup]$
- 追加重定向
[test@localhost dup]$ echo "Hello" >> linux
[test@localhost dup]$ cat linux
linux
Hello
[test@localhost dup]$ #并沒有清空檔案內容而是追加在后面
重定向的本質如圖所示:

重定向介面 int dup2(int oldfd,int newfd)
流程:
- 關閉1號檔案描述符
1.1 關閉成功(回傳檔案描述符),才能進行第二步
1.2 關閉失敗(回傳-1),不能進行第二步,重定向失敗 - newfd,拷貝oldfd所描述的檔案資訊
代碼演示:
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
//打開檔案,不存在則創建
int fd = open("./linux",O_CREAT | O_RDWR,0664);
if(fd<0)
{
perror("open");
}
printf("fd:%d\n",fd);
//將標準輸出重定向到檔案當中
//dup2(int oldfd,int newfd)
//oldfd --> fd(上面打開檔案的檔案描述符) newfd --> 1(標準輸出)
// dup2(fd,1);
int ret_d = dup2(fd,1);
printf("ret_d:%d\n",ret_d);//回傳的是1這個檔案描述符
//成功以后,下面的代碼在往標準輸出當中進行輸出的時候,就是往檔案當中寫了
printf("Hello World\n");
// close(fd);
while(1)
{
sleep(1);
}
return 0;
}
//輸出結果:
fd:3 //之后陷入死回圈,為了方便查看檔案描述資訊

可以發現已經將標準輸出重定向到了檔案當中,驗證一下是否列印到了檔案當中
[test@localhost dup]$ cat linux
ret_d:1
Hello World #可以看到dup2函式之后的兩條列印陳述句都列印到了linux這個檔案中
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/276983.html
標籤:其他
上一篇:大學四年我想要做的是什么?
下一篇:考研雜談
