【Linux】基礎IO
FILE *fp = fopen("file", "rwa+"); //開啟一個檔案 linux中的'\n'就是'\n' 而windows中的'\n'會替換'\r\n' C中fopen建立一個檔案abc,假設abc中有內容,再次建立abc檔案時,內容會被覆蓋掉。
C庫函式
fgetc(fp); fputc(fp); //讀/寫一個字元 fgets(buf, len, fp); fputs(buf, len, fp); //讀/寫一行 fscanf(fp, "%s", buf); fprintf(fp, "%s", buf); //按照給定的格式讀/寫 fread(buf, size, num, fp); fwrite(buf, size, num, fp); //讀寫 size*num才是最終要讀寫檔案的大小 //size表示一次讀寫檔案的大小,num表示次數 feof(fp); //判斷是否讀取到檔案結束,如果讀取結束,則返回真,反之則返回假。 fclose(fp); //關閉一個檔案
C中關於檔案的操作的庫函式的返回值實際上是一個結構體指標。 // 從字串中讀進與指定格式相符的資料 int sscanf(const char *str, const char *format, ...); 其中的format可以是一個或多個 {%[*] [width] [{h | l | I64 | L}]type | ' ' | '/t' | '/n' | 非%符號} sscanf 與 scanf 的區別 前者以固定的字串作為輸入源,後者以stdin作為輸入源。 * 亦可用於格式中, (即 %*d 和 %*s) 加了星號 (*) 表示跳過此資料不讀入. (也就是不把此資料讀入引數中) {a|b|c}表示a,b,c中選一,[d],表示可以有d也可以沒有d。 特別的:%*[width] [{h | l | I64 | L}]type 表示滿足該條件的被過濾掉,不會向目標引數中寫入值 %[a-z] 表示匹配a到z中任意字元,貪婪性(儘可能多的匹配) %[aB'] 匹配a、B、'中一員,貪婪性 %[^a] 匹配非a的任意字元,貪婪性 檔案描述符:作業系統通過一個整數來 代表開啟的檔案,將這個數稱為檔案描述符。 OS在開啟檔案表中維護的開啟檔案狀態和資訊。 windows中稱為檔案控制代碼(handler)
open files 程序能夠開啟的檔案描述符的個數是 [0, ulimit -n] //開啟檔案 int open(const char *pathname, int flags) 引數 pathname 要開啟的檔案 flags 開啟方式 可以理解為點陣圖(某一位置為1) O_RDONLY : 只讀方式開啟 #define O_RDONLY 00 必須選擇一個O_WRONLY : 只寫方式開啟 #define O_WRONLY 01 第一位置為1 且只能選一個O_RDWR : 讀寫方式開啟 #define O_RDWR 02 第二位置為1 O_TRUNC : 清空檔案 #define O_TRUNC 01000 原來的檔案中有內容,加上這個選項可以在建立檔案的時候清除檔案內容。 O_APPEND : 追加檔案 #define O_APPEND 02000 O_CREAT : 建立檔案 #define O_CREAT 0100 0表示八進位制,這裡表示的是第7位置為1(3個二進位制位表示一個八進位制位) 返回值 失敗返回-1,成功返回檔案描述符 stdin 0 stdout 1 stderr 2
//指定建立檔案的許可權 int open(const char *path, int flags, mode_t mode); path 要建立的檔名 flags O_CREAT | O_RDWR O_CREAT | O_EXCL O_EXCL與O_CREAT放在一起才會生效 #define O_EXCL 0200 一定要加上O_CREAT 如果檔案存在就報錯,否則就建立。 mode 許可權0644 0666 建立檔案的許可權還會受到umask影響 O_CREAT | O_RDWR 表示新建一個檔案可讀寫,此時再次執行該程式碼時,新建的檔案並不會覆蓋掉之前的檔案。 從0開始第一個沒有被檔案描述符使用的數字 因此開啟檔案返回的檔案描述符是3
//功能:從fd檔案中讀取資料到buf所指向的空間,該空間的大小為len。 //讀取檔案 //返回實際讀取的位元組數 int read(int fd, char *buf, size_t len); fd 要讀取的檔名 buf 緩衝區 len 最多讀取字元 期望讀取多少位元組的資料
//往fd所指向的檔案中寫入資料,資料的其實地址為buf,大小為len int write(int fd, char *buf, size_t len);
EEXIST == errno EEXIST表示檔案存在報錯的返回值
//關閉檔案 int close(int fd);
vimdiff a b 比較a檔案與b檔案的差異
^M 實際上就是ctrl+v ctrl+m 注意要在編輯模式下才會出現,藍色的。 解決windows端拷貝資料到linux vim環境中出現^M 換行 :%s/^M//g 即可替換為空
//定位 //返回值:新的位置相對於檔案開頭的偏移量 int lseek(int fd, off_t offset, int whence); 偏移量 偏移量的基準位置 SEEK_SET 開始 SEEK_CUR 當前 SEEK_END 結尾 //獲取檔案大小 lseek(fd, 0, SEEK_END); 求檔案大小struct stat sbuf; sbuf.st_size struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for filesystem I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond precision for the following timestamp fields. For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* time of last access */ struct timespec st_mtim; /* time of last modification */ struct timespec st_ctim; /* time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */ #define st_mtime st_mtim.tv_sec #define st_ctime st_ctim.tv_sec };
C庫函式之查詢 int fseek(FILE *stream, long offset, int whence); long ftell(FILE * stream); //告訴stream在哪找 od -c test.c 檢視每個字元的位置 也就是所有字元都可顯(tab以及空格等等) ACE網路庫 char *想象成快取也就是char [] lseek(fd, 1024*1024*1024, SEEK_CUR); 表示在當前位置處偏移1G 實際上SEEK_CUR後面的1G都是空洞檔案hole(灰色地帶,不存資料)。 ls -l檢視檔案大小,此時顯示1個多G,實際上這些是虛擬記憶體,是假的,但是OS會提前把這麼大的記憶體佔用(有可能以後會用到), du -h可以檢視其真正儲存在磁碟上的大小。 df -h檢視磁碟佔用 du -h檢視檔案真正佔用磁碟的大小
> abc 表示把空檔案輸出重定向到abc檔案中 du -h檢視檔案大小發現是0 開啟的檔案會在PCB中建立一張表(或者說一個結構體)fds(ps:這張表的大小在32位機上是32),如果開啟檔案個數大於32,那麼會動態 開闢一塊記憶體,在這張fds表中,每建立一個檔案,就會在表中建立一個編號(是個指標),實際上也就是檔案描述符,該編號指向一張 檔案表(ps:一個結構體),檔案表中存有開啟標誌(方式:讀/寫)、當前讀寫位置(應用:lseek偏移2位,位置就加2)、refcnt(引用計數) (ps:表示有多少個檔案描述符指向檔案表)(fork建立一個子程序後,子程序會複製父程序的資料,同時指向這張檔案表,那麼refcnt就變成了2) 檔案表中的一個指標指向vnode表,vnode表包括很多資訊(ls -l 獲取的檔案許可權,建立時間,檔案大小等等資訊(stat結構),除了檔名), vnode的一個指標指向了inode表(index 索引表,可以知道資料在磁碟上哪塊存著),C庫函式中的檔案操作函式,其結構體一定會包含檔案描述符, 因為read時,就通過檔案描述符訪問到檔案表、vnode表以及inode表的資訊。 父程序徹底結束的話,會釋放掉PCB,但是不會關閉檔案表,每開啟一個檔案都對應一個檔案表。 close一個檔案,refcnt就會減去1,直到refcnt減到0,才會釋放掉檔案表。 fork建立一個子程序後,子程序會複製父程序的資料,同時指向這張檔案表,那麼refcnt就變成了2,當關閉父程序後, refcnt減去1,子程序不會受到影響,仍然指向檔案表,假如關掉了檔案表,那麼子程序寫資料時就會報錯(因為檔案表中的當前讀寫位置不存在)。 檔案描述符就是從0開始的整數。當我們開啟檔案時,作業系統在記憶體中要建立相應的資料結構來描述目標檔案。於是就有了 file結構體。它表示一個已經開啟的檔案物件,而程序執行open系統呼叫,所以必須讓程序和檔案關聯起來。每個程序都有一個指標 *files, 指向一張檔案表files_struct,該表最重要的部分就是包含一個指標陣列,每個元素都是一個指向開啟檔案的指標! 所以,本質上,檔案描述符就是該陣列的下標。只要拿著檔案描述符,就可以找到對應的檔案。
在/usr/include/libio.h
struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags // 緩衝區相關 /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ 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. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain;
fopen() 與 open()
typedef struct _IO_FILE { int cnt; // 當前位置到末尾還剩下多少字元還沒有讀取 char *base; // 基準位置,使用者空間內開闢的一片快取,base指向起始地址 char *ptr; // 當前位置,指向當前正在讀寫位置 int fd; // 系統呼叫的返回值 }FILE; fopen() { FILE *fp = malloc(); fp->fd = open(); return fp; } 需要注意的是:呼叫C庫函式時,由於不停地切換使用者空間到核心空間,會帶來很大的系統開銷。 鑑於不停地切換空間,C庫函式於是就先開闢出來一片快取供使用,這樣每次讀取就可以了,不需要切換空間。 僅讀取檔案的話,使用C庫函式讀取效率更高一點。 C庫提供的IO是帶快取的,而OS提供的沒有。 fread()更有效率一點,這是因為一開始獲取到檔案描述符3對應的檔案表後,會先把base指向的快取區填滿,不需要每次切換核心態和使用者態。 read(fd, buf, 1024) read一次讀取磁碟最多為4K時效率最高,也就是記憶體頁的大小。 int ftruncate(int fd, off_t length); ftruncate 截斷檔案大小 (下載電影,比如說大小4G,開始下載時,就會先佔用4G的記憶體,產生一個hole檔案,然後再慢慢下載) truncate命令用來改變檔案大小 檔案定位函式 rewind 反繞 fseek 隨機定位