文章目錄
- 基礎IO
- 1. C語言函式操作檔案
- 2. 系統呼叫函式操作檔案
- 2.1 open函式
- 2.2 read函式
- 2.3 write函式
- 2.4 lseek函式
- 2.5 close函式
- 2.6 測驗程式
- 3. 檔案描述符
- 3.1 查看檔案描述符
- 3.2 PCB與檔案描述符的關系
- 3.3 分配檔案描述符規則
- 3.4 檔案描述符泄漏問題
- 4.檔案描述符 和 檔案流指標 的對比
- 5.重定向
- 5.1 清空重定向 >
- 5.2 追加重定向 >>
- 5.3 重定向原理
- 6. ext2檔案系統
- 7. 軟鏈接 和 硬鏈接
- 7.1 軟鏈接
- 7.2 硬鏈接
- 7.3 inode對比
基礎IO
1. C語言函式操作檔案
C語言檔案操作博客
| C庫IO函式 | 功能描述 |
|---|---|
| FILE *fopen(const char *path, const char *mode); | 通過一個檔案流指標打開一個流并與檔案相關聯 |
| size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); | 將ptr指向的資料寫入流指標指向的流中 |
| size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); | 從流指標指向的流中讀取資料保存在ptr指向的位置 |
| int fseek(FILE *stream, long int offset, int whence); | 重新定位檔案流指標從whence為起始位置偏移offset個字符 |
| int fclose(FILE *stream); | 關閉檔案流指標指向的流 |
2. 系統呼叫函式操作檔案
| 系統呼叫函式 | 功能描述 |
|---|---|
| int open(const char *pathname, int flags, mode_t mode) | 將檔案名轉換為一個新建的檔案描述符,通過檔案描述符操作檔案 |
| ssize_t read(int fd, void *buf, size_t count); | 將檔案中的內容讀取到指定空間中保存 |
| ssize_t write(int fd, const void *buf, size_t count); | 將buf中的內容寫入檔案中 |
| off_t lseek(int fd, off_t offset, int whence); | 將與檔案描述符相關聯的檔案的偏移量重新定位到引數偏移量處 |
| int close(int fd); | 關閉一個檔案描述符 |
2.1 open函式
int open(const char *pathname, int flags, mode_t mode)
功能:將檔案名轉換為一個新建的檔案描述符,通過檔案描述符操作檔案
頭檔案:
- fcntl.h
引數:
pathname:要打開的檔案名稱(路徑+名稱)
flags : 以某種方式打開,在原始碼中以宏定義
必選宏(選擇其中一個) 含義 O_RDONLY 只讀方式打開 O_WRONLY 只寫方式打開 O_RDWR 讀寫方式打開
可選宏(非必選項 ) 含義 O_APPEND 追加 O_TRUNC 截斷 O_CREAT 檔案不存在則創建 其中必選宏與可選宏之間使用按位或的方式連接,例如 O_RDONLY | O_CREAT
mode : 給檔案設定權限,傳入8進制數字
回傳值:
- -1 打開失敗
- >= 0 唯一標識檔案的檔案描述符
Q : 為什么宏之間要按位或?
原始碼中的宏定義:定義位于/usr/include/bits/fcntl-linux.h檔案中,其中數字都是8進制
#define O_RDONLY 00
#define O_WRONLY 01
#define O_RDWR 02
#define O_CREAT 0100
| 宏 | 8進制 | 2進制 |
|---|---|---|
| O_RDONLY | 00 | 00000000 00000000 00000000 00000000 |
| O_WRONLY | 01 | 00000000 00000000 00000000 00000001 |
| O_RDWR | 02 | 00000000 00000000 00000000 00000010 |
| O_CREAT | 0100 | 00000000 00000000 00000000 01000000 |
O_RDWR | O_CREAT
2.2 read函式
ssize_t read(int fd, void *buf, size_t count);
功能:將檔案中的內容讀取到指定空間中保存
頭檔案:
- unistd.h
引數:
- fd : 檔案描述符,open函式的回傳值
- buf : 將檔案中的內容讀到buf中
- coount : 最大可以讀多少個
回傳值:回傳讀取到的位元組數量
2.3 write函式
ssize_t write(int fd, const void *buf, size_t count);
功能:將buf中的內容寫入檔案中
頭檔案:
- unistd.h
引數:
- fd : 檔案描述符,open函式的回傳值
- buf : 要寫入檔案的內容
- count : 要寫入檔案內容的大小
回傳值:寫成功的位元組數量
2.4 lseek函式
off_t lseek(int fd, off_t offset, int whence);
功能:將與檔案描述符相關聯的檔案的偏移量重新定位到引數偏移量處
頭檔案:
- unistd.h
引數:
- fd : 檔案描述符,open函式的回傳值
- offset : 偏移量
- whence : 偏移起始位置
宏 含義 SEEK_SET 檔案內容起始位置 SEEK_CUR 當前位置 SEEK_END 檔案內容結束為止 回傳值:偏移量
2.5 close函式
int close(int fd);
功能:關閉一個檔案描述符
頭檔案:
- unistd.h
引數:
- fd : 檔案描述符,open函式的回傳值
回傳值:
- 0 表示關閉成功
- -1 表示有錯誤發生
2.6 測驗程式
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
//1.測驗open函式:打開檔案并創建檔案描述符
int fd = open("./testfile",O_RDWR | O_CREAT, 0664);
if(fd < 0)
perror("open");
else
{
printf("1.創建檔案成功,檔案描述符為fd = %d\n",fd);
}
//2.測驗write函式:向檔案中寫入資料
const char* w_buf = "Hello World!";
int w_ret = write(fd,w_buf,strlen(w_buf));
if(!w_ret)
perror("write");
else
printf("2.寫入成功,寫入了%d個位元組的資料\n",w_ret);
//3.測驗lseek函式:重定位
int offset = 0;
int l_ret = lseek(fd,offset,SEEK_SET);
printf("3.偏移量為%d\n",l_ret);
//4.測驗read函式:從檔案中讀取資料
char r_buf[1024] = { 0 };
int r_ret = read(fd,r_buf,sizeof(r_buf) - 1);
if(!r_ret)
perror("read");
else
printf("4.讀取成功,讀取了%d個位元組的資料,讀取內容為%s\n",r_ret,r_buf);
//5.測驗close函式:關閉檔案描述符
int c_ret = close(fd);
if(!c_ret)
printf("5.檔案描述符%d關閉成功\n",fd);
else
perror("close");
return 0;
}
[gongruiyang@localhost TestSysFile]$ gcc test.c -o test
[gongruiyang@localhost TestSysFile]$ ./test
1.創建檔案成功,檔案描述符為fd = 3
2.寫入成功,寫入了12個位元組的資料
3.偏移量為0
4.讀取成功,讀取了12個位元組的資料,讀取內容為Hello World!
5.檔案描述符3關閉成功
[gongruiyang@localhost TestSysFile]$ ls
test test.c testfile
[gongruiyang@localhost TestSysFile]$ ll
總用量 20
-rwxrwxr-x. 1 gongruiyang gongruiyang 8872 12月 31 11:56 test
-rw-rw-r--. 1 gongruiyang gongruiyang 1159 12月 31 11:56 test.c
-rw-rw-r--. 1 gongruiyang gongruiyang 12 12月 31 11:56 testfile
3. 檔案描述符
3.1 查看檔案描述符
Q:如何找到已經打開的檔案描述符呢?
A:
在關閉檔案描述符之前使用while回圈,不讓程式關閉檔案描述符
使用ps查看打開檔案描述符的行程號
使用cd /proc/行程號/fd 進入檔案描述符檔案夾
例如:查看行程號為6606打開的檔案描述符
[gongruiyang@localhost TestSysFile]$ ps -aux | grep test
gongrui+ 6606 0.0 0.0 4216 348 pts/0 S+ 12:02 0:00 ./test
gongrui+ 6686 0.0 0.0 112828 976 pts/1 R+ 12:02 0:00 grep --color=auto test
[gongruiyang@localhost TestSysFile]$ cd /proc/6606/fd
gongruiyang@localhost fd]$ ll
總用量 0
lrwx------. 1 gongruiyang gongruiyang 64 12月 31 12:08 0 -> /dev/pts/0
lrwx------. 1 gongruiyang gongruiyang 64 12月 31 12:08 1 -> /dev/pts/0
lrwx------. 1 gongruiyang gongruiyang 64 12月 31 12:02 2 -> /dev/pts/0
lrwx------. 1 gongruiyang gongruiyang 64 12月 31 12:08 3 -> /home/gongruiyang/ClassLinunx/TestSysFile/testfile
- 作業系統會為每一個行程在磁盤當中創建一個以行程號命名的檔案夾,在該檔案夾中有一個fd檔案夾,保存的資訊為該行程打開的檔案描述符資訊,
- 當我們新創建出來一個新的行程,一定會打開3個檔案描述符,分別對應 標準輸入(0) 標準輸出(1) 標準錯誤(2)
3.2 PCB與檔案描述符的關系
PCB的task_struct與檔案描述符關系原始碼與圖解:
task_struct部分原始碼
struct task_struct {
..........
/* open file information */
struct files_struct *files;
..........
};
files_struct部分原始碼
struct files_struct {
/*
* read mostly part
*/
atomic_t count;
struct fdtable *fdt;
struct fdtable fdtab;
/*
* written part on a separate cache line in SMP
*/
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;
struct embedded_fd_set close_on_exec_init;
struct embedded_fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT];
};
圖解:

由原始碼可以看出:
- 每一個行程的task_struct結構體中都包含一個指向files_struct結構體的指標,files_struct結構體中有一個fd_array陣列,該陣列中保存的都是指向file結構體的指標,每一個file結構體對應一個檔案
- 檔案描述符其實就是內核當中fd_array陣列的下標
- fd_array陣列中前三個資料元素對應的就是:標準輸入 標準輸出 標準錯誤
結論:每創建一個新行程,系統默認會打開三個檔案描述符:標準輸入 標準輸出 標準錯誤
3.3 分配檔案描述符規則
規則:最小未占用原則
意思就是將從0開始往上尋找第一個未被占用的檔案描述符分配給正在打開的檔案
3.4 檔案描述符泄漏問題
檔案描述符也叫檔案句柄
當我們打開一個檔案,作業系統就會檔案分配一個檔案描述符,如果在使用完畢之前,沒有及時的關閉檔案,就會造成檔案句柄泄漏的問題
Q : 一個行程當中最大打開的檔案數量是多少?
A : 使用ulimit命令
[gongruiyang@localhost ClassLinunx]$ ulimit -a
................
open files (-n) 1024
................
可在輸出列印中找到open files最大數為1024
該最大值是可以修改的
演示代碼
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int count = 0;
while(1)
{
int fd = open("testfile",O_RDWR | O_CREAT, 0664);
if(fd < 0)
{
perror("open");
break;
}
else
{
printf("fd:%d\n",fd);
count++;
}
}
printf("count = %d\n",count);
return 0;
}
fd:1023
open: Too many open files
count = 1021
4.檔案描述符 和 檔案流指標 的對比
檔案流指標:fopen函式回傳的,檔案流指標是由C庫維護的
檔案描述符:open函式回傳的,檔案描述符是由作業系統維護的
檔案流指標原始碼:源檔案路徑/usr/include/stdio.h
typedef struct _IO_FILE FILE;
由此可以看出FILE是_IO_FILE的別名,我們再來看看_IO_FILE的部分原始碼:源檔案路徑:/usr/include/libio.h
struct _IO_FILE {
...............
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
int _fileno;
...............
};
由_IO_FILE結構體原始碼可以了解到,讀寫緩沖區分別使用了3個char型指標,并且用_fileno保存檔案描述符
總結:檔案流指標將檔案描述符進行了封裝
針對檔案流指標而言的緩沖區,是C庫維護的
exit函式在退出執行緒的時候,會重繪緩沖區,原因是操作的是檔案流指標
_exit哈數在退出執行緒的時候,不會重繪緩沖區,原因是_exit是系統呼叫,內核無法涉及到C庫維護的緩沖區,所以不會重繪
5.重定向
5.1 清空重定向 >
將輸出位置調整到檔案中,每一次從終端向檔案輸入資料前都會將先前的內容清空
[gongruiyang@localhost TestDup]$ echo "123"
123
[gongruiyang@localhost TestDup]$ echo "123" > testfile
[gongruiyang@localhost TestDup]$ cat testfile
123
[gongruiyang@localhost TestDup]$ echo "456" > testfile
[gongruiyang@localhost TestDup]$ cat testfile
456
5.2 追加重定向 >>
將輸出位置調整到檔案中,每一次從終端向檔案輸入資料都會被追加在源檔案內容之后
[gongruiyang@localhost TestDup]$ echo "123" >> testfile
[gongruiyang@localhost TestDup]$ cat testfile
123
[gongruiyang@localhost TestDup]$ echo "456" >> testfile
[gongruiyang@localhost TestDup]$ cat testfile
123
456
5.3 重定向原理
echo本來是要將資料通過終端行程的 標準輸出[1] 將資料列印輸出,經過重定向后,1號檔案描述符中指向file結構體的指標從原來指向的檔案改變指向重定向后的檔案
重定向介面:
int dup2(int oldfd, int newfd);
功能:dup2() makes newfd be the copy of oldfd, closing newfd first if necessary
頭檔案:
- unistd.h
引數:
- oldfd :
- newfd :
回傳值:重定向成功回傳newfd,失敗回傳-1
演示程式:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int fd = open("./testfile",O_RDWR | O_CREAT,06664);
if(fd < 0)
perror("open");
else
{
//將標準輸出重定向到檔案中去
dup2(fd,1);
}
printf("test message!\n");
return 0;
}
[gongruiyang@localhost TestDup]$ gcc test.c -o test
[gongruiyang@localhost TestDup]$ ./test
[gongruiyang@localhost TestDup]$ cat testfile
test message!
6. ext2檔案系統
ext2檔案系統

- Block Bitmap : 本質是一個位圖,每一個位元位表示Data blocks中資料塊的使用情況,若位元位為1,則表示占用,若位元位為0,表示未被占用
- inode Bitmap : 本質是一個位圖,每一個位元位表示inode table當中inode塊的使用情況,若位元位為1,則表示占用,若位元位為0,表示未被占用
- Data blocks : 實際存盤檔案的區域,在這個區域中,將磁盤分成了不同的小block
- inode Table : inode結點的集合,inode結點描述了檔案的存盤情況(檔案在哪些Block塊中存盤的)
檔案存盤程序:
- 去Block Bit Map區域查找空閑的block塊,將檔案存盤在空閑的block塊當中
- 通過inode BitMap獲取空閑的inode節點,通過inode節點去描述檔案在Data Block區域當中存盤的位置
- inode+檔案名稱作為目錄的目錄項被保存下來
存盤原理:分散存盤,相比于線性存盤,減少了許多磁盤碎片

檔案的獲取程序:
- 通過檔案名+inode節點號找到inode對應的檔案資訊
- 在Data blocks區域當中獲取當前檔案存盤的內容,在進行拼接,拼接完成之后就是檔案內容了
7. 軟鏈接 和 硬鏈接
7.1 軟鏈接
軟鏈接檔案就相當于一個檔案的快捷方式
特點:
軟鏈接檔案具有獨立的inode節點號
洗掉源檔案時,應當將軟鏈接到該檔案的軟鏈接檔案一同洗掉,否則會出現如下情況
創建軟鏈接命令:
ln -s 原檔案名 軟鏈接檔案名
例如:
[gongruiyang@localhost TestLink]$ ln -s sourceFile softLinkFile
[gongruiyang@localhost TestLink]$ ll
總用量 0
lrwxrwxrwx. 1 gongruiyang gongruiyang 10 12月 31 23:28 softLinkFile -> sourceFile
-rw-rw-r--. 1 gongruiyang gongruiyang 0 12月 31 23:25 sourceFile
7.2 硬鏈接
硬鏈接檔案就相當于源檔案的一個副本,除了檔案名外,其他一模一樣
創建硬鏈接命令:
ln 原檔案名 硬鏈接檔案名
例如:
[gongruiyang@localhost TestLink]$ ln sourceFile hardLinkFile
[gongruiyang@localhost TestLink]$ ll
總用量 0
-rw-rw-r--. 2 gongruiyang gongruiyang 0 12月 31 23:38 hardLinkFile
-rw-rw-r--. 2 gongruiyang gongruiyang 0 12月 31 23:38 sourceFile
7.3 inode對比
[gongruiyang@localhost TestLink]$ ls -li
總用量 0
33554820 -rw-rw-r--. 2 gongruiyang gongruiyang 0 12月 31 23:38 hardLinkFile
33554821 lrwxrwxrwx. 1 gongruiyang gongruiyang 10 12月 31 23:43 softLinkFile -> sourceFile
33554820 -rw-rw-r--. 2 gongruiyang gongruiyang 0 12月 31 23:38 sourceFile
由上述命令輸出結果可知:
inode[sourceFile] = 33554820
inode[softLinkFile] = 33554821
inode[hardLinkFile] = 33554820
總結:軟鏈接檔案的inode與源檔案不同,硬鏈接檔案的inode與源檔案相同
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/243530.html
標籤:其他



