學習筆記 參考鏈接1 、參考鏈接2以及百度百科
在進行C語言學習的時候我們了解到了C語言相關的一些IO操作,如fopen,fwrite,fread,fprintf,fclose等相關函數,他們都是由C庫函數提供的一些函數,是將操作系統的系統調用加以封裝,雖說Linux是由C語言實現的,但為了使我們更加的了解Linux,就需要了解更接近與底層的一些IO操作,因此就需要來了解下基本的系統調用—open,write,read,close
首先我們來了解下open,write,read,close的系統調用
open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
open有三個參數
pathname:要打開或創建的目標文件名
flags:對文件進行多種操作也就有有多個參數,這多個參數可以進行或運算,即就是flags
參數:
O_RDONLY:只讀打開
O_WRONLY:只寫打開
O_RDWR:讀,寫打開
O_CREAT:若文件不存在,創建文件
O_APPEND:追加寫
參數1,2,3,必須制定一個且只能制定一個,使用參數4,必須使用open的第三個參數mode:新文件的訪問權限
返回值:成功:新打開文件的文件描述符(fd)
失敗:-1
write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd:文件描述符
buf:寫入的緩沖區
count:寫的字符長度,也就是看你需要寫多少
返回值:
如果順利write()會返回實際寫入的字節數。當有錯誤發生時則返回-1,錯誤代碼存入errno中。
read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd:文件描述符
buf:讀入的緩沖區
count:寫的字符長度,也就是看你需要寫多少
返回值
如果順利read()會返回實際寫入的字節數。當有錯誤發生時則返回-1,錯誤代碼存入errno中。
close
#include <unistd.h>
int close(int fd);
close的參數就相對簡單了,這一個操作是不能遺漏的,只要了使用fd就必須close它
在這幾個函數中都涉及到了關鍵的參數fd,因此要了解這幾個函數,就必須先了解下文件描述符(fd)。
什么是文件描述符,這是一個相對抽象的概念,我們先來看看下面這張圖
在PCB結構體中存在一個files指針,它指向一個file_struct
結構體,而在file_struct
結構體中存在一個file* fd
數組,這個數組里面存放的是file指針,用來指向不同的file文件,而fd就可以理解為這個指針數組的下標,因此要打開一個文件,我們就可以拿到該文件的fd就可以了。
fd的分配原則:
在files_struct
數組當中,使用沒有被使用的最小下標,作為新的文件描述符。
操作系統默認使用了該數組的前三個元素,0號下標指向標準輸入(stdin
),1號下標指向標準輸出(stdout
),2號下標指向標準錯誤(stderr
)。
因此正常情況下,新的fd都是從3開始的,但如果我們關閉了默認的fd,新的文件的fd就從關閉的fd處開始。
說到了fd,我們就不得不來區分下FILE
和fd
FILE是C庫當中提供的一個結構體,而fd是系統調用,更加接近于底層,因此FILE中必定封裝了fd。
我們可以來看看FILE的結構體:
typedef struct _IO_FILE FILE
;在/usr/include/stdio.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;int _fileno;//fd的封裝
可以看到int_fileno就是對fd的封裝,在這一部分的開頭有一大段跟緩沖區相關的內容,為什么要諾列出它呢,首先可以來看一個很詭異的例子:
#include <stdio.h> #include <string.h>#include <unistd.h>#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>int main(){const char *msg1 = "hello printf\n";const char *msg2 = "hello fwrite\n";const char *msg3 = "hello write\n";printf(msg1);fwrite(msg2, 1, strlen(msg2), stdout);write(1, msg3, strlen(msg3));fork();return 0;}
運行結果:
[rlh@localhost test]$ ./hello
hello printf
hello fwrite
hello write
但當我們對進程實現輸出重定向,你就會發現詭異的事情:
[rlh@localhost test]$ ./hello > file
[rlh@localhost test]$ cat file
hello write
hello printf
hello fwrite
hello printf
hello fwrite
這是為什么呢,這是跟C庫的緩沖數據有關,C庫緩沖數據分為三種(1)、無緩沖(2)、行緩沖(3)、全緩沖。
行緩沖就是往顯示器上寫,全緩沖就是往文件里寫。
在上面的現象中,write不受影響是因為它屬于系統調用,沒有緩沖區,而printf和fwrite會自帶緩沖區,當發生重定向到普通文件的時候,它就會從行緩沖轉變為全緩沖,也就是會往文件里面寫寫,但是我們緩沖區里的數據,即使fork也不會立即被刷新,當進程退出后會統一刷新,寫入文件,但是fork的時候會發生寫時拷貝,也就是當父進程準備刷新的時候,子進程就已經有了一份相同的數據,所以就會產生上面的現象。
了解下重定向。
重定向分為三種:
輸出重定向(>) 也就是關閉fd為1下標所指向的內容
輸入重定向(<) 同理就是關閉fd為0下標所指向的內容
追加重定向(>>) 后面多一個追加選項
stat函數
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *file_name, struct stat *buf);
函數說明: 通過文件名filename獲取文件信息,并保存在buf所指的結構體stat中
返回值: 執行成功則返回0,失敗返回-1,錯誤代碼存于errno
錯誤代碼:
ENOENT 參數file_name指定的文件不存在
ENOTDIR 路徑中的目錄存在但卻非真正的目錄
ELOOP 欲打開的文件有過多符號連接問題,上限為16符號連接
EFAULT 參數buf為無效指針,指向無法存在的內存空間
EACCESS 存取文件時被拒絕
ENOMEM 核心內存不足
ENAMETOOLONG 參數file_name的路徑名稱太長
eg:
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>int main() {struct stat buf;stat("/etc/hosts", &buf);printf("/etc/hosts file size = %d/n", buf.st_size);
}
struct stat {dev_t st_dev; //文件的設備編號ino_t st_ino; //節點mode_t st_mode; //文件的類型和存取的權限nlink_t st_nlink; //連到該文件的硬連接數目,剛建立的文件值為1uid_t st_uid; //用戶IDgid_t st_gid; //組IDdev_t st_rdev; //(設備類型)若此文件為設備文件,則為其設備編號off_t st_size; //文件字節數(文件大小)unsigned long st_blksize; //塊大小(文件系統的I/O 緩沖區大小)unsigned long st_blocks; //塊數time_t st_atime; //最后一次訪問時間time_t st_mtime; //最后一次修改時間time_t st_ctime; //最后一次改變時間(指屬性)
};
先前所描述的st_mode 則定義了下列數種情況:
S_IFMT 0170000 文件類型的位遮罩S_IFSOCK 0140000 scoketS_IFLNK 0120000 符號連接S_IFREG 0100000 一般文件S_IFBLK 0060000 區塊裝置S_IFDIR 0040000 目錄S_IFCHR 0020000 字符裝置S_IFIFO 0010000 先進先出S_ISUID 04000 文件的(set user-id on execution)位S_ISGID 02000 文件的(set group-id on execution)位S_ISVTX 01000 文件的sticky位S_IRUSR(S_IREAD) 00400 文件所有者具可讀取權限S_IWUSR(S_IWRITE)00200 文件所有者具可寫入權限S_IXUSR(S_IEXEC) 00100 文件所有者具可執行權限S_IRGRP 00040 用戶組具可讀取權限S_IWGRP 00020 用戶組具可寫入權限S_IXGRP 00010 用戶組具可執行權限S_IROTH 00004 其他用戶具可讀取權限S_IWOTH 00002 其他用戶具可寫入權限S_IXOTH 00001 其他用戶具可執行權限
上述的文件類型在POSIX中定義了檢查這些類型的宏定義:
S_ISLNK (st_mode) 判斷是否為符號連接S_ISREG (st_mode) 是否為一般文件S_ISDIR (st_mode) 是否為目錄S_ISCHR (st_mode) 是否為字符裝置文件S_ISBLK (s3e) 是否為先進先出S_ISSOCK (st_mode) 是否為socket