Linux系統之基礎IO
學過C語言的都知道,在C語言中IO其實就是檔案操作,像fopen,fwrite,fread,fseek,fprintf,fclose等都是C語言庫函式中的系統呼叫介面,如下圖是常用
先回顧下這些標準c庫中檔案I/O的介面的使用
回顧C庫中的IO
FILE *fopen(const char *path, const char *mode);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
它的返回值:檔案流指標
檔案流指標 | stdin | stdout | stderr | FILE* |
---|---|---|---|---|
檔案描述符 | 0 | 1 | 2 | 3 |
一個程序執行起來之後預設打開了三個檔案,關於檔案描述符我在後面有介紹
下面演示一個C語言庫中函式的使用
#include <stdio.h> #include <string.h> int main() { FILE *fp = NULL; fp = fopen("./test.txt", "w"); if (fp == NULL) { printf("fopen error!!\n"); return -1; } char *ptr = "apple!!"; fwrite(ptr, strlen(ptr), 1, fp); fseek(fp, 10, SEEK_END); fwrite(ptr, strlen(ptr), 1, fp); fseek(fp, 0, SEEK_SET); char buff[1024] = {0}; fread(buff, 1024, 1, fp); printf("buff:[%s]\n", buff); fprintf(fp, "\n%s-%d-%s\n", "apple", 3, "i love to eat!!"); fclose(fp); return 0; }
系統IO
以下是一些系統呼叫IO介面
int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); ssize_t write(int fd, const void *buf, size_t count); int creat(const char *pathname, mode_t mode); off_t lseek(int fd, off_t offset, int whence); //off_t lseek(int fd, off_t offset, int whence); // fd: 檔案描述符 // offset:偏移值 // whence:偏移起始位置 // SEEK_SET 檔案起始位置 // SEEK_CUR 當前讀寫位置 // SEEK_END 檔案末尾位置 fileno將檔案流指標轉換成檔案描述符 fdopen 將檔案描述符轉換成檔案流指標
檔案描述符
檔案描述符是一個數字,那麼一個數字是如何描述檔案的呢?
一個程序要對所有開啟的檔案進行管理,先將檔案描述起來,然後組織管理 程序中對檔案程序描述的 結構體叫file(struct file) 程序使用了一個結構體陣列來組織這些描述,而檔案的描述符就是這個結構體陣列的下標
pcb中對檔案的描述的一個機構圖(file)陣列的下標 系統呼叫open返回的是檔案描述符:int 而c庫中fopen返回的是一個檔案流指標:FILE*
檔案描述分配符規則: 尋找最小下未使用的下標
檔案描述符fd返回值:正確:非負值(檔案描述符) 錯誤:-1
緩衝區
我們所說的緩衝區(print),使用者態的一個緩衝區,是檔案流指標自帶的fprint,fwrite這些庫函式 都是先把資料寫入到緩衝區中,等緩衝區寫滿或者一些其他條件滿足後才真正寫入到檔案中, 而系統呼叫沒有這個使用者態的緩衝區,(是直接寫入到檔案中)
/*
* open close read write lseek
*/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h> // 檔案控制標頭檔案
int main()
{
//int open(const char *pathname, int flags, mode_t mode);
// pathname: 要開啟的檔名
// flags:選項標誌
// O_RDONLY 只讀
// O_WRONLY 只寫
// O_RDWR 讀寫
//
// O_CREAT 檔案不存在則建立,存在則開啟
// O_EXCL 與O_CREAT同用,若檔案存在則報錯
// O_TRUNC 開啟檔案的同時,清空檔案原有內容
// O_APPEND 追加
// mode:檔案許可權,用於建立檔案的時候
int fd = -1;
fd = open("./tmp.txt", O_RDWR | O_CREAT, 0777);
if (fd < 0) {
perror("open error");
return -1;
}
//ssize_t write(int fd, const void *buf, size_t count);
// fd: 檔案描述符
// buf:要寫入的資料
// count:要寫入的資料長度
// 返回值:實際的寫入資料長度
char *ptr = "apple!!\n";
ssize_t wlen = write(fd, ptr, strlen(ptr));
if (wlen < 0) {
perror("write error");
return -1;
}
lseek(fd, 0, SEEK_SET);
//read:返回實際讀取到的資料長度
char buff[1024] = {0};
ssize_t rlen = read(fd, buff, 1024 - 1);
if (rlen < 0) {
perror("read error");
return -1;
}
printf("buf:[%s]\n", buff);
close(fd);
return 0;
}
檔案重定向
原理:將原本描述符所對應的下標的檔案修改成另外一個檔案,描述符沒有變,但是真正通過描述符操作的這個檔案已經改變了
int dup2(int oldfd, int newfd);
/* 演示檔案描述符分配規則的demo
* 1. 檔案描述符是一個數字,並且這個數字是一個結構體陣列的下標
* 分配規則:
* 尋找最小的未使用的下標
* 一個程序執行起來之後,預設開啟的三個檔案:
* 標準輸入 標準輸出 標準錯誤
* stdin stdout stderr
* 0 1 2
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int fd = -1;
fd = open("./tmp.txt", O_RDWR | O_CREAT);
if (fd < 0) {
perror("open error");
return -1;
}
dup2(fd, 1);
printf("fd:%d\n", fd);
fflush(stdout);
close(fd);
return 0;
}
Linux下的檔案系統
linux檔案系統是由兩層結構構建:第一層是虛擬檔案系統(VFS),第二層是各種不同的具體檔案系統。
VFS是把各種具體的檔案系統的公共部分抽取出來,形成一個抽象層,是系統核心的一部分。它位於使用者程式和具體的檔案系統中間。它對使用者
程式提供了標準的檔案系統的呼叫介面,對具體的檔案系統,它通過一系列的對不同檔案系統公用的函式指標來實際呼叫具體的檔案系統函式,完成實際 的各有的操作。任何使用檔案系統的程式必須經過這層介面來使用它。通過這種方式,VFS就對用於遮蔽了底層檔案系統的實現細節和差異。
通過 cat /proc/filesystems命令可以檢視系統支援哪些檔案系統。
open是系統呼叫級別的操作,呼叫VFS_open -> 底層的open(包括驅動的open,比如字元裝置驅動裡面的
file_opreations裡面的open等。聯想一下)
軟硬連結
軟硬連結區別:
- 1、可以針對目錄建立,硬連結不可
- 2、軟連結是一個新的檔案,而硬連結是原始檔的一個別名(跟原始檔使用相同的結點inode結點)
- 3、軟連結可以劃分區建立,但是硬連結可以
- 4、原始檔刪除後,軟連結找不到,但是硬連結沒有影響,僅僅是連結數減一
動態庫/靜態庫
動靜態庫連結: gcc預設連結方式:動態連結 ---- 連結是動態庫 靜態連結需要加 -static 的gcc連結選項 ---- 連結是靜態庫 如何自己生成一個動態庫 程式的編譯過程: 預處理--》編譯--》彙編--》連結 生成一個庫其實就是將所有程式碼打包起來,最終得到一個庫檔案
區別:
- 使用靜態庫時將靜態庫中程式碼全部拿過來
- 動態庫使用時記錄地址資訊
生成動態庫:gcc -- share test.c -o libtest.so 生成靜態庫:ar cr libtest.a test.c 編譯選項:-fPIC :產生位置無關程式碼
如何連結一個可執行程式:
gcc main.c -o main -L./ -ltest
- -L 用於指定庫的查詢路徑
- -l 用於指定連結庫的名稱