UNIX再学习 -- 文件I/O
生活随笔
收集整理的這篇文章主要介紹了
UNIX再学习 -- 文件I/O
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
上一篇講完文件描述符,接下來進入正題,文件的處理函數。
一、函數 open
詳細內容,可自行 man creat 查看 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);int creat(const char *pathname, mode_t mode);1、參數解析
第一個參數:字符串形式的路徑和文件名 第二個參數:操作標志 O_RDONLY 只讀模式 O_WRONLY 只寫模式 O_RDWR 讀寫模式 O_EXEC 只執行打開 O_SEARCH 只搜索打開(應用于目錄) 上述 5 種標志位是互斥的,也就是不可同時使用,但可與下列的標志位利用 OR(|) 運算符組合。 O_APPEND:每次寫時追加到文件的尾端。 O_CLOEXEC:把FD_CLOEXEC常量設置為文件描述符標志。 O_CREAT:若此文件不存在則創建它。使用此選項時,open函數需同時說明第3個參數mode(openat函數需說明第4個參數mode),用mode指定該新文件的訪問權限位。 O_DIRECTORY:如果path引用的不是目錄,則出錯。 O_EXCL:如果同時指定了O_CREAT,而文件已經存在,則出錯。用此可以測試一個文件是否存在,如果不存在,則創建此文件,這使測試和創建兩者稱為一個原子操作。 O_NOCTTY:如果path引用的是終端設備,則不將該設備分配作為此進程的控制終端。 O_NOFOLLOW:如果path引用的是一個符號鏈接,則出錯。 O_NONBLOCK:如果path引用的是一個FIFO、一個特殊文件或一個字符特殊文件,則此選項為文件的本次打開操作和后序的I/O操作設置非阻塞方式。 O_SYNC :使每次write等待物理I/O操作完成,包括由該write操作引起的文件屬性更新所需的I/O。 O_TRUNC:如果此文件存在,而且為只讀或讀-寫成功打開,則將其長度截斷為0。 O_TTY_INIT:如果打開一個還未打開的終端設備,設置非標準termios參數值,使其符合Single UNIX Specification(以及POSIX.1)中同步輸入和輸出選項的一部分。 O_DSYNC:使每次write要等待物理I/O操作完成,但是如果該寫操作并不影響讀取剛寫入的數據,則不需要等待文件屬性被更新。 O_RSYNC:使每一個以文件描述符作為參數進行的read操作等待,直至所有對文件同一部分掛起的寫操作都完成。 第三個參數:操作模式 僅當創建新文件時才使用,用于指定創建的新文件權限。可以使用宏定義或者八進制文件權限碼。 注:此為Linux2.2以后特有的標志位,以避免一些系統安全問題。參數mode 則有下列數種組合,只有在建立新文件時才會生效,此外真正建文件時的權限會受到umask值所影響,因此該文件權限應該為(mode-umaks)。(1)八進制文件權限碼(常用)
詳解參看:C語言再學習 -- 修改linux文件權限# ls -la 總用量 20 drwxrwxr-x 2 tarena tarena 4096 Mar 27 14:44 . drwxrwxr-x 4 tarena tarena 4096 Mar 14 11:14 .. -rwxr-xr-x 1 root root 7158 Mar 27 14:44 a.out -rw-r--r-- 1 root root 79 Mar 27 14:44 test.c 以 test.c 為例: - 代表文件類型 rw 代表屬主 r 代表屬組 r 代表其它 1 代表硬鏈接數 root 代表屬主名稱 root 代表屬組名稱 79 代表文件大小 Mar 27 14:44 代表文件最后修改時間 test.c 代表文件名 按照:0 表示沒有權限,1 表示可執行權限,2 表示可寫權限,4 表示可讀權限,然后將其相加。所以數字屬性的格式應為 3 個從 0 到 7 的八進制數,其順序是文件屬主(u)、與文件屬組(g)、其他用戶(o)。
因此上面,test.c 的權限為 0644 注:可以在程序中用八進制方式表示數字必須以 0 作為開頭,采用?%o 作為占位符可以把一個整數的八進制表示方式打印在屏幕上?。
(2)宏定義方式
可查看 /usr/include/linux/stat.h 看到具體定義S_IRWXU, 00700權限,代表該文件所有者具有可讀、可寫及可執行的權限。 S_IRUSR 或 S_IREAD, 00400權限,代表該文件所有者具有可讀取的權限。 S_IWUSR 或 S_IWRITE,00200權限,代表該文件所有者具有可寫入的權限。 S_IXUSR 或 S_IEXEC, 00100權限,代表該文件所有者具有可執行的權限。S_IRWXG 00070權限,代表該文件用戶組具有可讀、可寫及可執行的權限。 S_IRGRP 00040權限,代表該文件用戶組具有可讀的權限。 S_IWGRP 00020權限,代表該文件用戶組具有可寫入的權限。 S_IXGRP 00010權限,代表該文件用戶組具有可執行的權限。S_IRWX O00007權限,代表其他用戶具有可讀、可寫及可執行的權限。 S_IROTH 00004權限,代表其他用戶具有可讀的權限 S_IWOTH 00002權限,代表其他用戶具有可寫入的權限。 S_IXOTH 00001權限,代表其他用戶具有可執行的權限。同八進制方法格式一樣,不過就顯得尤為復雜了,例如:設置權限 0644:S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
2、返回值:
成功返回一個新的文件描述符,失敗返回 -1。文件描述符就是非負的整數,用于代表一個打開的文件。3、錯誤代碼
EEXIST 參數pathname 所指的文件已存在, 卻使用了O_CREAT 和O_EXCL 旗標. EACCESS 參數pathname 所指的文件不符合所要求測試的權限. EROFS 欲測試寫入權限的文件存在于只讀文件系統內. EFAULT 參數pathname 指針超出可存取內存空間. EINVAL 參數mode 不正確. ENAMETOOLONG 參數 pathname 太長. ENOTDIR 參數pathname 不是目錄. ENOMEM 核心內存不足. ELOOP 參數pathname 有過多符號連接問題. EIO I/O 存取錯誤.4、函數功能
打開/創建 一個文件/設備。5、與 openat 函數區別
可自行 man openat 查看 openat 的詳細內容,在此不多講解了。openat 函數是 POSIX.1 最新版本中新增的一類函數之一,希望解決兩個問題。第一,讓線程可以使用相對路徑名打開目錄中的文件,而不再只能打開當前工作目錄。第二,可以避免time-of-check-to-time-of-use(TOCTTOU)錯誤。6、與 creat 函數區別
調用creat函數創建一個新文件。返回值:若成功,返回只寫打開的文件描述符;若出錯,返回-1。此函數等效于:open (path, O_RDWR | O_CREAT | O_TRUNC, mode);
二、函數 close
#include <unistd.h> int close(int fd);1、函數功能:
close()關閉一個文件描述符,所以它不再引用任何文件,可以重用。 任何記錄鎖(見fcntl(2))舉行與該進程關聯并擁有的文件被刪除(不管用于獲取的文件描述符)鎖)。如果 fd 是引用底層打開文件描述的最后一個文件描述符(見open(2)),則與打開的文件描述被釋放; 如果描述符是使用 unlink(2)文件刪除的文件的最后引用被刪除。
2、示例說明
//open 函數和 close 函數的使用 #include <stdio.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h>int main (void) { //文件不存在則創建,存在則打開 // int fd = open ("a.txt", O_RDONLY | O_CREAT, 0644);//文件不存在則創建,存在則創建失敗 // int fd = open ("a.txt", O_RDONLY | O_CREAT | O_EXCL, 0644);//文件存在,且為普通文件,打開方式有寫權限,則清空文件 int fd = open ("a.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (-1 == fd)perror ("open"), exit (-1);printf ("打開文件成功!\n");printf("fd = %d\n", fd);int res = close (fd);if (-1 == res)perror ("cosee"), exit (-1);printf("關閉文件成功!\n");return 0; } 輸出結果: 打開文件成功! fd = 3 關閉文件成功!三、函數 lseek
可參看:C語言再學習 -- 文件 每個打開文件都有一個與其相關聯的“當前文件偏移量”。它通常是一個非負數,用以度量從文件開始處計算的字節數。通常,讀、寫操作都從當前文件偏移量處開始,并使偏移量增加所讀寫的字節數。按系統默認的情況,當打開一個文件時,除非指定 O_APPEND 選項,否則該偏移量被設置為 0. 可以調用 lseek 顯式地為一個打開文件設置偏移量。 #include <sys/types.h> #include <unistd.h> off_t lseek (int fd, off_t offset, int whence);1、參數解析
第一個參數:文件描述符 第二個參數:偏移量 第三個參數:從什么地方開始偏移 若whence是SEEK_SET,則將該文件的偏移量設置為距文件開始處offset個字節。?若whence是SEEK_CUR,則將該文件的偏移量設置為其當前值加offset,offset可為正或負。?
若whence是SEEK_END,則將該文件的偏移量設置為文件長度加offset,offset可正可負。
偏移起始位置:文件頭0(SEEK_SET),當前位置1(SEEK_CUR),文件尾2(SEEK_END))為基準,偏移offset(指針偏移量)個字節的位置。
2、返回值
若成功,返回新的文件偏移量;若出錯,返回為 -1。為此可以用下列方式確定打開文件的當前偏移量: off_t currpos; currpos = lseek(fd, 0, SEEK_CUR);這種方法也可用來確定所涉及的文件是否可以設置偏移量。如果文件描述符指向的是一個管道、FIFO或網絡套接字,則 lseek 返回 -1,并將 errno 設置為 ESPIPE (Illegal seek)。3、函數功能
表示調整文件的讀寫位置。4、示例說明
//示例一 #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <sys/stat.h>int main (void) {int fd = open ("a.txt", O_RDWR);if (-1 == fd)perror ("open"), exit (-1);printf ("打開文件成功!\n");printf ("ABCDEFGHIJKLMN\n");char c;//第一次讀 從開頭讀取 Aread (fd, &c, sizeof (char));printf ("c = %c\n", c);//第二次讀,移到下一個 Bread (fd, &c, sizeof (char));printf ("c = %c\n", c);printf ("-------------------\n");//SEEK_CUR 當前位置讀取 (2+0 = 2)(CDE)lseek (fd, 2L, SEEK_CUR);read (fd, &c, sizeof (char));printf ("c = %c\n", c);//SEEK_CUR 當前位置讀取(-2+0 = -2) (DEF)lseek (fd, -2L, SEEK_CUR);read (fd, &c, sizeof (char));printf ("c = %c\n", c);//SEEK_SET 從開頭位置讀取,(3 + 1 = 4)(ABCD)lseek (fd, 3L, SEEK_SET);read (fd, &c, sizeof (char));printf ("c = %c\n", c);//SEEK_SET 從開頭位置讀取,(-5 + 1 = -4)錯誤,指向當前位置lseek (fd, -5L, SEEK_SET);read (fd, &c, sizeof (char));printf ("c = %c\n", c);//從結尾位置讀取,(-3 + 2 = -1) (MN)lseek (fd, -3L, SEEK_END);read (fd, &c, sizeof (char));printf ("c = %c\n", c);close (fd);printf ("關閉文件成功!\n");return 0; } 輸出結果: 打開文件成功! ABCDEFGHIJKLMN c = A c = B ------------------- c = E c = D c = D c = E c = M 關閉文件成功!//示例二 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() {if (lseek (STDIN_FILENO, 0, SEEK_CUR) == -1)printf ("cannot seek\n");else printf ("seek OK\n");return 0; } 使用重定向測試: # ./a.out < /etc/passwd seek OK# cat < /etc/passwd | ./a.out cannot seek# ./a.out < /var/spool/cron/FIFO cannot seek//示例三 #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <sys/stat.h> #include <string.h>int main (void) {int fd = open ("a.txt", O_RDWR | O_APPEND);//nt fd = open ("a.txt", O_RDWR);if (-1 == fd)perror ("open"), exit (-1);printf ("打開文件成功!\n");char c;lseek (fd, 3, SEEK_SET);read (fd, &c, sizeof (char));printf ("c = %c\n", c);char buffer[5] = "1234";if(write(fd,buffer,strlen(buffer)) !=strlen(buffer))perror("write error"), exit(-1);close (fd);return 0; } 查看 a.txt # cat a.txt ABCDEFJHIJKLMN 1234說明: (1)示例一,說明偏移起始位置:文件頭0(SEEK_SET),當前位置1(SEEK_CUR),文件尾2(SEEK_END))為基準,偏移offset(指針偏移量)個字節的位置。lseek()函數允許將文件偏移量設置為超出文件結尾(但不會更改文件的大小)。如果偏移量超出范圍,則打印當前位置。 (2)示例二,說明如果文件描述符指向的是一個管道、FIFO或網絡套接字,則 lseek 返回 -1。比較 lseek 的返回值時,不要測試它是否小于 0,而要測試它是否等于 -1。 (3)示例三,說明按系統默認的情況,當打開一個文件時,除非指定?O_APPEND?選項,否則該偏移量被設置為 0。 O_APPEND的含義是在每次寫之前,都講標志位移動到文件的末端。而?O_APPEND 打開后,是一個原子操作:移動到末端,寫數據,跟位移 lseek 無關。5、與 fseek 區別
int fseek(FILE *stream, long offset, int fromwhere); off_t lseek (int fd, off_t offset, int whence);fseek 函數和 lseek 函數類似,但 lseek 返回的是一個 off_t 數值,而 fseek 返回的是一個整型。一個打開的是文件流指針、一個打開的是文件描述符。
6、off_t 類型
查看:/usr/include/i386-linux-gnu/sys/types.h?
#ifndef __off_t_defined # ifndef __USE_FILE_OFFSET64 typedef __off_t off_t; # else typedef __off64_t off_t; # endif # define __off_t_defined #endif如果定義了__USE_FILE_OFFSET64 就把 off_t 定義為 __off64_t ,否則定義為 32 位。
使用 gdb 查看:
編譯gdb文件 # gcc -g test.c# gdb a.out GNU gdb (Ubuntu/Linaro 7.4-2012.02-0ubuntu2) 7.4-2012.02 Copyright (C) 2012 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-linux-gnu". For bug reporting instructions, please see: <http://bugs.launchpad.net/gdb-linaro/>... Reading symbols from /home/tarena/project/c_test/a.out...done. (gdb) ptype off_t type = long int
所以,linux中的 off_t 類型默認是 32 位的 long int。
四、函數 read
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);1、參數解析
第一個參數:文件描述符第二個參數:緩沖區的首地址第三個參數:讀取的數據大小2、返回值
成功返回讀取到的數據大小,若已到文件尾,返回 0,失敗返回 -1。很多種情況可使實際讀到的字節數少于要求讀的字節數:
(1)讀普通文件時,在讀到要求字節數之前已經到達了文件尾端。
(2)當從終端設備讀時,通常一次最多讀一行。
(3)當從網絡讀時,網絡中的緩沖機制可能造成返回值小于所要求讀的字節數。
(4)當從管道或FIFO讀時,如若管道包含的字節少于所需的數量,那么read將只返回實際可用的字節數。
(5)當從某些面向記錄的設別(如磁帶)讀時,一次最多返回一個記錄。
(6)當一信號造成中斷,而已經讀了部分數據量時。
讀操作從文件的當前偏移量處開始,在成功返回之前,該偏移量將增加實際讀到的字節數。
3、函數功能
表示從指定的文件中讀取指定的數據。4、示例說明
#include <stdio.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h>int main (void) {char buffer[100];int fd = open("a.txt", O_RDWR);lseek (fd, 2L, SEEK_SET);int size = read (fd, buffer, sizeof (buffer));printf ("buffer = %ssize = %d\n", buffer, size);close (fd);return 0; } 輸出結果: buffer = CDEFJHIJKLMN size = 13 說明:讀操作從文件的當前偏移量處開始,在成功返回之前,該偏移量將增加實際讀到的字節數。五、函數 write
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);1、參數解析
第一個參數:文件描述符第二個參數:緩沖區首地址第三個參數:數據的大小2、返回值
成功返回寫入的數據大小,失敗返回 -1。其返回值通常與 count 的值相同,否則表示出錯。write 出錯的一個常見原因是磁盤已寫滿,或者超過了一個給定進程的文件長度限制。對于普通文件,寫操作從文件的當前偏移量處開始。如果在打開文件時,指定了O_APPEND選項,則在每次寫操作之前,將文件偏移量設置在文件的當前結尾處。在一次成功寫之后,該文件偏移量增加實際寫的字節數。
3、函數功能
表示向指定的文件中寫入指定的數據4、示例說明
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <sys/stat.h> #include <string.h>int main (void) {//int fd = open ("a.txt", O_RDWR | O_APPEND);int fd = open ("a.txt", O_RDWR);if (-1 == fd)perror ("open"), exit (-1);lseek (fd, 3, SEEK_SET);char buffer[5] = "1234";int size = write(fd,buffer,strlen(buffer));printf ("size = %d\n", size);close (fd);return 0; } 輸出結果: size = 4查看 a.txt # cat a.txt ABC1234HIJKLMN 說明:對于普通文件,寫操作從文件的當前偏移量處開始。如果在打開文件時,指定了O_APPEND選項,則在每次寫操作之前,將文件偏移量設置在文件的當前結尾處。在一次成功寫之后,該文件偏移量增加實際寫的字節數。六、I/O的效率
UNIX環境高級編程書上示例位置在?apue.3e/figlinks?比如本例的 fig3.5 #include "apue.h"#define BUFFSIZE 4096int main(void) {int n;char buf[BUFFSIZE];while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)if (write(STDOUT_FILENO, buf, n) != n)err_sys("write error");if (n < 0)err_sys("read error");exit(0); } 重定向操作:標準輸入 # ./a.out < 1.txt? 12345678重定向操作:標準輸出 # ./a.out > 2.txt? ABCDEF ^C 查看2.txt: # cat 2.txt? ABCDEF 上篇講文件描述符時講過,POSIX 定義了?STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 來代替 0、1、2。這三個符號常量的定義位于頭文件?<unistd.h> 查看 /usr/include/unistd.h /* Standard file descriptors. */ #define STDIN_FILENO 0 /* Standard input. */ #define STDOUT_FILENO 1 /* Standard output. */ #define STDERR_FILENO 2 /* Standard error output. */1、問題來了,STDIN_FILENO與 stdin的區別?
參看:UNIX下STDIN_FILENO與stdin區別(1)數據類型不一致
stdin 等類型為 FILE *STDIN_FILENO 等類型為 int
使用 stdin 的函數主要有:fread、fwrite、fclose等,基本上都以 f 開頭
使用 STDIN_FILENO 的函數有:read、write、close等
(2)層次不一致
stdin 等屬于標準I/O,高級的輸入輸出函數。在<stdio.h>。STDIN_FILENO 等是文件描述符,是非負整數,一般定義為 0, 1, 2,直接調用系統調用,在<unistd.h>。
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
stdin等屬于標準庫處理的輸入流,其聲明為 FILE 型的,對應的函數前面都有f開頭,如fopen/fread/fwrite/fclose 標準庫調用等
STDIN_FILENO等屬于系統API接口庫,其聲明為 int 型,是一個打開文件句柄,對應的函數主要包括 open/read/write/close 等系統級調用。
2、兩者關系
對于 stdin 等可以使用 fileno() 函數(用來取得參數 stream 指定的文件流所使用的文件描述符)來取得該文件流對應的文件描述符。fileno(stdin) = STDIN_FILENO = 0
fileno(stdout) = STDOUT_FILENO = 1
fileno(stderr) = STDERR_FILENO = 2
例如:#include "apue.h"#define BUFFSIZE 4096int main(void) {int n;char buf[BUFFSIZE];while ((n = read(fileno(stdin), buf, BUFFSIZE)) > 0)if (write(fileno(stdout), buf, n) != n)err_sys("write error");if (n < 0)err_sys("read error");exit(0); }
七、文件共享
UNIX系統支持在不同進程間共享打開文件。內核使用3種數據結構表示打開文件,它們之間的關系決定了在文件共享方面一個進程對另一個進程可能產生的影響。
1)每個進程在進程表中都有一個記錄項,記錄項中包含一張打開文件描述符表,可將其視為一個矢量,每個描述符占用一項。與每個文件描述符相關聯的是:?
? ? 文件描述符標志;?
? ? 指向一個文件表項的指針。
2)內核為所有打開文件維持一張文件表。每個文件表項包含:?
? ? 文件狀態標志(讀、寫、添寫、同步和非阻塞等);?
? ? 當前文件偏移量;?
? ? 指向該文件v節點表項的指針。
3)每個打開文件(或設備)都有一個v節點(v-node)結構。v節點包含了文件類型和對此文件進行各種操作函數的指針。對于大多數文件,v節點還包含了該文件的i節點(i-node,索引節點)。這些信息是打開文件時從磁盤上讀入內存的,所以,文件的所有相關信息都是隨時可用的。如i節點包含了文件的所有者、文件長度、指向文件實際數據塊在磁盤上所在位置的指針等。?例如:一個進程有兩個不同的打開文件:一個文件從標準輸入打開(文件描述符為 0),另一個從標準輸出打開(文件描述符為 1)。#include "apue.h" #define BUFFSIZE 4096 int main(void) { int n; char buf[BUFFSIZE]; while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0) if (write(STDOUT_FILENO, buf, n) != n) err_sys("write error"); if (n < 0) err_sys("read error"); exit(0); } 執行: # ./a.out < 1.txt > 2.txt # cat 1.txt 12345678 # cat 2.txt 12345678該進程對應的 3 張表之間的關系:
再如:兩個進程各自打開了同一個文件,這部分參看:Linux中的文件描述符與打開文件之間的關系對于以上數據結構,其相關操作的說明如下:在完成每個 write 后,在文件表項中的當前文件偏移量即增加所寫入的字節數。如果這導致當前文件偏移量超出了當前文件長度,則將 i 節點表項中的當前文件長度設置為當前文件偏移量(即該文件增加了);
如果用 O_APPEND 標志打開一個文件,則相應標志也被設置到文件表項的文件狀態標志中。每次對這種具有追加寫標志的文件執行寫操作時,文件表現中的當前文件偏移量首先會被設置為 i 節點表現中的文件長度。這就使得每次寫入的數據都追加到文件的當前尾端處;
若一個文件用 lseek 定位到文件當前的尾端,則文件表項中的當前文件偏移量被設置為 i 節點表項中的當前文件長度;
lseek函數只修改文件表項中的當前文件偏移量,不進行任何I/O操作。
八、函數 dup 和 dup2
1、函數 dup
#include <unistd.h> int dup(int oldfd);函數功能: 表示根據參數指定的文件描述符進行拷貝, 返回值: 成功返回新的文件描述符,失敗返回 -1。 注意:文件描述符的復制本質上就是讓多個文件描述符對應同一個文件表,也就是對應同一個文件。由 dup 返回的心的文件描述符一定是當前可用文件描述符中的最小數值。 示例: #include <stdio.h> #include <unistd.h> #include <stdlib.h> int main (void) {int newfd = dup (STDOUT_FILENO);if (-1 == newfd)perror ("Fail to dup"), exit (-1);printf ("newfd = %d\n", newfd);write (newfd, "hello world\n", 12);return 0; } 輸出結果: newfd = 3 hello world
2、函數 dup2
#include <unistd.h> int dup2(int oldfd, int newfd);函數功能:表示將 newfd 作為參數 oldfd 的拷貝,如果 newfd 已經打開,則先將其關閉;如若 oldfd 等于 newfd,則 dup2 返回 newfd,而不關閉它。否則,newfd 的 FD_CLOEXEC 文件描述符標志被清除,這樣 newfd 在進程調用 exec 時是打開狀態。 返回值: 成功返回新描述符,也就是 newfd,失敗返回 -1。 示例: //dup/dup2函數的使用 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int main(void) {//1.打開/創建一個文件int fd = open("d.txt",O_RDWR|O_CREAT/*|O_EXCL*/,0644);if(-1 == fd){perror("open"),exit(-1);}printf("fd = %d\n",fd);//3//2.使用dup函數復制文件描述符// int fd2 = fd;int fd2 = dup(fd);if(-1 == fd2){perror("dup"),exit(-1);}printf("fd2 = %d\n",fd2);//4//3.針對不同的描述符進行處理write(fd,"A",1);write(fd2,"a",1);//打開/創建一個新文件int fd3 = open("e.txt",O_RDWR|O_CREAT,0644);if(-1 == fd3){perror("open"),exit(-1);}printf("fd3 = %d\n",fd3);//5//使用dup2函數進行描述符的拷貝// fd 到 fd3的拷貝 fd3 和 fd4相等int fd4 = dup2(fd,fd3);printf("fd3 = %d,fd4 = %d\n",fd3,fd4);// 5 5 write(fd3,"1",1); //d.txtwrite(fd4,"2",1); //d.txtwrite(fd,"3",1); //d.txt//4.關閉所有描述符close(fd);close(fd2);close(fd3);return 0; }輸出結果: fd = 3 fd2 = 4 fd3 = 5 fd3 = 5,fd4 = 5查看:d.txt 和 e.txt # cat d.txt Aa123 # cat e.txt 為空od 命令查看 # od -c d.txt? 0000000 ? A ? a ? 1 ? 2 ? 3 0000005 擴展:使用 od 命令觀察文件的實際內容。命令行中的 -c 標志表示以字符方式打印文件內容。 參看:od 命令
九、函數 fcntl?
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ );1、參數解析
第一個參數:文件描述符 第二個參數:操作的命令 ? ? F_DUPFD/F_DUPFD_CLOEXEC??復制文件描述符的功能,尋找最小的有效的大于等于第三個參數 arg 的描述符作為新的描述符。 ? ? ? ? 與 dup2 函數有所不同的是,不會強制關閉已經被占用的描述符。 ? ? F_GETFD/F_SETFD ?獲取/設置文件描述符的標志 ? ? F_GETFL/F_SETFL ?獲取/設置文件狀態的標志 ???F_GETOWN/F_SETOWN??獲取/設置異步 I/O 所有權? ? F_SETLK/F_GETLK ?加鎖/解鎖/測試鎖是否存在 第三個參數:可變長參數 參數是否需要,主要取決于參數 cmd
2、返回值
F_DUPFD ?成功返回新的文件描述符 F_GETFD ?成功返回文件描述符的標志值 F_GETFL ?成功返回文件狀態的標志值 F_GETOWN ?成功返回異步 I/O 所有權 其他操作成功返回 0, 所有的操作失敗返回 -13、函數功能
主要表示根據文件描述符對文件執行的操作 (1)復制文件描述符 (2)獲取/設置文件描述符的標志 (3)獲取/設置文件狀態的標志 (4)獲取/設置異步 I/O 所有權 (5)實現文件鎖的功能4、功能講解
(1)F_DUPFD?
復制文件描述符 fd。新的文件描述符作為函數值返回。它是尚未打開的各描述符中大于或等于第 3 參數值中各值的最小值。新描述符與 fd 共享同一文件表項。但是,新描述符有它自己的一套文件描述符,其中 FD_CLOEXEC 文件描述符標志被消除(這表示該描述符在 exec 時仍保持有效)。實際上:調用 dup (fd); ?等效于 fcntl (fd, F_DUPFD, 0); 而調用 dup2 (fd, fd2); ?等效于 close (fd2); ?fcntl (fd, F_DUPFD, fd2); 在后一種情況下,dup2 并不完全等同于 close 加上 fcntl。它們之間的區別具體如下: --dup2 是一個原子操作,而 close 和 fcntl 包括兩個函數調用。有可能在 close 和 fcntl 之間調用了信號捕獲函數,它可能修改文件描述符。如果不同的線程改變了文件描述符的話也會出現相同的問題。 --dup2 和 fcntl 有不同的 errno。 示例說明:? //示例一 #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h>int main (void) {int fd = fcntl (STDOUT_FILENO, F_DUPFD, 0);if (-1 == fd)perror ("Fail to fcntl"), exit (-1);printf ("fd = %d\n", fd);write (fd, "hell world\n", 12);return 0; } 輸出結果: fd = 3 hell world //示例二 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int main(void) {//1.打開/創建一個文件int fd = open("d.txt",O_RDWR|O_CREAT/*|O_EXCL*/,0644);if(-1 == fd){perror("open"),exit(-1);}printf("fd = %d\n",fd);//3//2.使用dup函數復制文件描述符// int fd2 = fd;int fd2 = dup(fd);if(-1 == fd2){perror("dup"),exit(-1);}printf("fd2 = %d\n",fd2);//4//3.針對不同的描述符進行處理write(fd,"A",1);write(fd2,"a",1);//打開/創建一個新文件int fd3 = open("e.txt",O_RDWR|O_CREAT,0644);if(-1 == fd3){perror("open"),exit(-1);}printf("fd3 = %d\n",fd3);//5close (fd3);//使用fcntl函數進行描述符的拷貝// fd 到 fd3的拷貝 fd3 和 fd4相等int fd4 = fcntl (fd, F_DUPFD, fd3);printf("fd3 = %d,fd4 = %d\n",fd3,fd4);// 5 5 write(fd3,"1",1); //d.txtwrite(fd4,"2",1); //d.txtwrite(fd,"3",1); //d.txt//4.關閉所有描述符close(fd);close(fd2);close(fd3);return 0; } 輸出結果: fd = 3 fd2 = 4 fd3 = 5 fd3 = 5,fd4 = 5od命令查看: # od -c d.txt 0000000 A a 1 2 3 0000005
(2)F_DUPFD_CLOEXEC
復制文件描述符,設置與新描述符關聯的 FD_CLOEXEC 文件描述符標志的值,返回新文件描述符。(3)F_GETFD
對應于 fd 的文件描述符標志作為函數值返回。當前只定義了一個文件描述符標志 FD_CLOEXEC。1)上面出現了好幾次 FD_CLOEXEC 文件描述符標志,需要對它了解一下:
參看:執行時關閉標識位 FD_CLOEXEC 的作用 FD_CLOEXEC 是“文件描述符”的標志,用來設置文件的 close-on-exec 狀態。此標志用來控制在執行 exec 后,是否關閉對應的文件描述符(關閉文件描述符即不能對文件描述符指向的文件進行任何操作)
2)這里又有個新概念 exec :
exec 應該是指 exec系列函數,在此只了解其中的 execl 函數: #include <unistd.h> int execl(const char *path, const char *arg, ...);execl() 其中后綴 "l" 代表 list 也就是參數列表的意思,第一參數path字符指針所指向要執行的文件路徑, 接下來的參數代表執行該文件時傳遞的參數列表:argv[0],argv[1]... 最后一個參數須用空指針NULL作結束。返回值:如果執行成功則函數不會返回, 執行失敗則直接返回-1, 失敗原因存于errno 中. 函數功能:主要用于實現跳轉的功能
示例: #include <unistd.h> int main (void) {// 執行/bin目錄下的ls, 第一參數為程序名ls, 第二個參數為"-al", 第三個參數為"/etc/passwd"execl("/bin/ls", "ls", "-al", "/etc/passwd", NULL);return 0; } 輸出結果: -rw-r--r-- 1 root root 1918 Dec 1 10:28 /etc/passwd
3)FD_CLOEXEC 標志使用
第一、此標志用來控制在執行 exec 后,是否關閉對應的文件描述符
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h>int main (int argc, char *argv[]) {if (argc > 1){int flags = fcntl (STDOUT_FILENO, F_GETFD);if (-1 == flags)perror ("fail to F_GETFD"), exit (-1);flags |= FD_CLOEXEC;if (fcntl (STDOUT_FILENO, F_SETFD, flags) == -1)perror ("fail to F_SETFD"), exit (-1);}execl("/bin/ls", "ls", "-al", "/etc/passwd", NULL);return 0; } 輸出結果: 不含有FD_CLOEXEC標志,未關閉對應的文件描述符 STDOUT_FILENO # ./a.out -rw-r--r-- 1 root root 1918 Dec 1 10:28 /etc/passwd含有FD_CLOEXEC標志,關閉了對應的文件描述符 STDOUT_FILENO # ./a.out 1 ls: 寫入錯誤: 錯誤的文件描述符第二、在使用fork調用的子進程中,此描述符并不關閉,仍可使用。(驗證失敗)
//vfork函數和execl函數的使用 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <fcntl.h>int main (int argc, char *argv[]) {if (argc > 1){int flags = fcntl (STDOUT_FILENO, F_GETFD);if (-1 == flags)perror ("fail to F_GETFD"), exit (-1);flags |= FD_CLOEXEC;if (fcntl (STDOUT_FILENO, F_SETFD, flags) == -1)perror ("fail to F_SETFD"), exit (-1);}//1.使用vfork函數創建子進程pid_t pid = vfork();if(-1 == pid)perror("vfork"),exit(-1);//2.子進程調用execl函數跳轉if(0 == pid) //子進程{printf("子進程%d開始運行\n",getpid());sleep(2);//調用execl函數進行跳轉execl("/bin/ls", "ls", "-al", "/etc/passwd", NULL);//執行不到printf("子進程結束\n");}sleep (2);//3.父進程執行printf("父進程%d開始執行\n",getpid());printf("父進程結束\n");return 0; } 不含有FD_CLOEXEC標志,未關閉對應的文件描述符 STDOUT_FILENO # ./a.out 子進程3488開始運行 -rw-r--r-- 1 root root 1918 Dec 1 10:28 /etc/passwd 父進程3487開始執行 父進程結束含有FD_CLOEXEC標志,關閉了對應的文件描述符 STDOUT_FILENO # ./a.out 1 子進程3550開始運行 ls: 寫入錯誤: 錯誤的文件描述符 父進程3549開始執行 父進程結束4)總結:
FD_CLOEXEC 用來設置文件的 close-on-exec 狀態標準。在 exec() 調用后,close-on-exec 標志為0的情況,此文件不被關閉。非零則在 exec() 后被關閉。默認close-on-exec狀態為 0,需要通過FD_CLOEXEC設置。 看見有這句話,“在使用fork調用的子進程中,此描述符并不關閉,仍可使用。”但是我驗證了下,在子進程中也是可以關閉的。 (不過還是不太確認,如果哪位看到這里,可以告訴我一下,這句話是否正確)。(4)F_SETFD
對于 fd 設置文件描述符標志。新標志值按第 3 個參數(取為整型值)設置。示例同上。
(5)F_GETFL
對應于 fd 的文件狀態標志作為函數值返回。下面是 open 函數時描述的文件狀態標志。| O_RDONLY | 只讀打開 |
| O_WRONLY | 只寫打開 |
| O_RDWR | 讀、寫打開 |
| O_EXEC | 只執行打開 |
| O_SEARCH | 只搜索打開目錄 |
| O_APPEND | 追加寫 |
| O_NONBLOCK | 非阻塞模式 |
| O_SYNC | 等待寫完成(數據和屬性) |
| O_DSYNC | 等待寫完成(僅數據) |
| O_RSYNC | 同步讀和寫 |
| O_FSYNC | 等待寫完成(僅FreeBSD和Mac OS X) |
| O_ASYNC | 異步I/O(僅FreeBSD和Mac OS X) |
(6)F_SETFL
將文件狀態標志設置為第 3 個參數的值(取為整型值)。可以更改的幾個標志是:O_APPEND、O_NONBLOCK、O_SYNC、O_DSYNC、O_RSYNC、O_FSYNC和 O_ASYNC。 以 O_NONBLOCK 為例: //示例一 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main (void) { //1.打開/創建一個文件 int fd = open ("a.txt", O_RDONLY | O_NONBLOCK); if (-1 == fd) perror("open"),exit(-1); int flags = fcntl (fd, F_GETFL, 0); printf ("flags = %d\n", flags); //將其設為非阻塞 flags &= ~O_NONBLOCK;fcntl (fd, F_SETFL, flags);int flags1 = fcntl (fd, F_GETFL, 0); printf ("flags1 = %d\n", flags1); close(fd); return 0; } 輸出結果: flags = 2048 flags1 = 0 //示例二 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main (void) { //1.打開/創建一個文件 int fd = open ("a.txt", O_RDONLY); if (-1 == fd) perror("open"),exit(-1); int flags = fcntl (fd, F_GETFL, 0); printf ("flags = %d\n", flags); flags |= O_NONBLOCK;fcntl (fd, F_SETFL, flags);int flags1 = fcntl (fd, F_GETFL, 0); printf ("flags1 = %d\n", flags1); close(fd); return 0; } 輸出結果: flags = 0 flags1 = 2048示例說明:
示例一:取消文件的某個flags,比如文件是非阻塞的,想設置成為阻塞:flags = fcntl(fd,F_GETFL,0); flags &= ~O_NONBLOCK; fcntl(fd,F_SETFL,flags);示例二:增加文件的某個flags,比如文件是阻塞的,想設置成非阻塞:
flags = fcntl(fd,F_GETFL,0); flags |= O_NONBLOCK; fcntl(fd,F_SETFL,flags);
(7)F_GETOWN
獲取當前接收 SIGIO 和 SIGURG 信號的進程 ID 或進程組 ID。(示例以后寫)(8)F_SETOWN
設置接收 SIGIO 和 SIGURG 信號的進程 ID 或進程組 IF。正的 arg 指定一個進程 ID,負的 arg 表示等于 arg 絕對值的一個進程組 ID。十、總結
文件I/O這一章講完,不過有幾處沒有搞懂和沒有講到的地方。 首先,進程表、文件表、v節點表這部分沒有搞明白;原子操作沒有講到;函數 sync、fsync和fdatasync沒有講;函數 fcntl 文件鎖部分沒有講;函數 ioctl沒有講;/dev/fd 沒有講。 這些在以后的總結中會涉及到,到時再詳細講吧。總結
以上是生活随笔為你收集整理的UNIX再学习 -- 文件I/O的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UNIX再学习 -- 文件描述符
- 下一篇: UNIX再学习 -- 文件和目录