 文件描述符)
目錄1.知識(shí)回顧2.文件描述符訪問(wèn)文件的本質(zhì)fd的數(shù)值分配規(guī)則規(guī)則1、規(guī)則2★規(guī)則3FILE結(jié)構(gòu)體驗(yàn)證stdin、stdout、stderr對(duì)應(yīng)的文件描述符方法1:使用_fileno方法2: 使用fileno()方法3:查看/dev目錄下的文件引用計(jì)數(shù)3.拓展閱讀4.補(bǔ)充練習(xí)題5.補(bǔ)充: Linux的終端文件1.知識(shí)回顧參見(jiàn)OS29.【Linux】文件IO (1) open、write和close系統(tǒng)調(diào)用文章2.文件描述符提出問(wèn)題:FILE*和文件描述符fd有什么關(guān)系?操作系統(tǒng)管理大量文件需要先描述再組織,而描述文件的各個(gè)屬性需要結(jié)構(gòu)體struct file,通常直接或間接包含以下信息:1.在磁盤(pán)中的位置2.基本屬性(權(quán)限、大小、讀寫(xiě)位置、誰(shuí)打開(kāi)的、......3.文件的內(nèi)核緩沖區(qū)信息struct file其結(jié)構(gòu)體的定義在在linux內(nèi)核v6.16.4的include/linux/fs.h中:/** * struct file - Represents a file * f_lock: Protects f_ep, f_flags. Must not be taken from IRQ context. * f_mode: FMODE_* flags often used in hotpaths * f_op: file operations * f_mapping: Contents of a cacheable, mappable object. * private_data: filesystem or driver specific data * f_inode: cached inode * f_flags: file flags * f_iocb_flags: iocb flags * f_cred: stashed credentials of creator/opener * f_owner: file owner * f_path: path of the file * f_pos_lock: lock protecting file position * f_pipe: specific to pipes * f_pos: file position * f_security: LSM security context of this file * f_wb_err: writeback error * f_sb_err: per sb writeback errors * f_ep: link of all epoll hooks for this file * f_task_work: task work entry point * f_llist: work queue entrypoint * f_ra: files readahead state * f_freeptr: Pointer used by SLAB_TYPESAFE_BY_RCU file cache (dont touch.) * f_ref: reference count */ struct file { spinlock_t f_lock; fmode_t f_mode; const struct file_operations *f_op; struct address_space *f_mapping; void *private_data; struct inode *f_inode; unsigned int f_flags; unsigned int f_iocb_flags; const struct cred *f_cred; struct fown_struct *f_owner; /* --- cacheline 1 boundary (64 bytes) --- */ struct path f_path; union { /* regular files (with FMODE_ATOMIC_POS) and directories */ struct mutex f_pos_lock; /* pipes */ u64 f_pipe; }; loff_t f_pos; #ifdef CONFIG_SECURITY void *f_security; #endif /* --- cacheline 2 boundary (128 bytes) --- */ errseq_t f_wb_err; errseq_t f_sb_err; #ifdef CONFIG_EPOLL struct hlist_head *f_ep; #endif union { struct callback_head f_task_work; struct llist_node f_llist; struct file_ra_state f_ra; freeptr_t f_freeptr; }; file_ref_t f_ref; /* --- cacheline 3 boundary (192 bytes) --- */ } __randomize_layout __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */訪問(wèn)文件的本質(zhì)在OS29.【Linux】文件IO (1) open、write和close系統(tǒng)調(diào)用文章提到過(guò):文件打開(kāi)需要借助進(jìn)程,可以推出:Linux的task_struct必定含有與文件相關(guān)的指針(task_struct的完整代碼參見(jiàn)OS17.【Linux】進(jìn)程基礎(chǔ)知識(shí)(1)文章),這里摘取一部分:struct task_struct { //...... struct files_struct *files; //...... {在linux內(nèi)核v6.16.4的include/linux/fdtable.h中,有定義打開(kāi)的文件的表結(jié)構(gòu)/* * Open file table structure */ struct files_struct { /* * read mostly part */ atomic_t count; bool resize_in_progress; wait_queue_head_t resize_wait; struct fdtable __rcu *fdt; struct fdtable fdtab; /* * written part on a separate cache line in SMP */ spinlock_t file_lock ____cacheline_aligned_in_smp; unsigned int next_fd; unsigned long close_on_exec_init[1]; unsigned long open_fds_init[1]; unsigned long full_fds_bits_init[1]; struct file __rcu * fd_array[NR_OPEN_DEFAULT]; };這里只研究struct file __rcu * fd_array[NR_OPEN_DEFAULT],__rcu是修飾符,這里不討論,NR_OPEN_DEFAULT宏是fd_array的初始化fd_array元素的個(gè)數(shù)現(xiàn)簡(jiǎn)化處理files_struct,認(rèn)為只含有fd_array:fd_array顧名思義,是存放struct file*的的數(shù)組,有下圖:struct file(注在include/linux/fs.h中定義了struct file)對(duì)象之間會(huì)用鏈?zhǔn)浇Y(jié)構(gòu)串聯(lián)起來(lái).方便增刪查改進(jìn)程每打開(kāi)一個(gè)文件時(shí),open內(nèi)部會(huì)將這個(gè)文件的struct file的指針添加到fd_array中,并返回fd_array下標(biāo)int open(const char *pathname, int flags, ... /* mode_t mode */ );fd的數(shù)值分配規(guī)則例如以下代碼:#include stdio.h #include unistd.h #include fcntl.h #include sys/stat.h int main() { umask(0); int fd1open(test1.txt,O_WRONLY|O_CREAT|O_APPEND,0666); if (fd10) { perror(open failed); return -1; } int fd2open(test2.txt,O_WRONLY|O_CREAT|O_APPEND,0666); if (fd20) { perror(open failed); return -1; } int fd3open(test3.txt,O_WRONLY|O_CREAT|O_APPEND,0666); if (fd30) { perror(open failed); return -1; } printf(test1.txt fd%d\n,fd1); printf(test2.txt fd%d\n,fd2); printf(test3.txt fd%d\n,fd3); close(fd1); close(fd2); close(fd3); return 0; }運(yùn)行結(jié)果:規(guī)則1、規(guī)則2規(guī)則1.打開(kāi)的文件默認(rèn)從3開(kāi)始編號(hào)規(guī)則2.fd0、1、2默認(rèn)被占用,因?yàn)檫M(jìn)程默認(rèn)打開(kāi)3個(gè)IO流,不是C語(yǔ)言的特性stdin: 鍵盤(pán)文件stdout: 顯示器文件stderr: 顯示器文件可以用和read和write函數(shù)嘗試向這些流寫(xiě)入:#include stdio.h #include unistd.h #include fcntl.h #include sys/stat.h #include string.h #define BUFFER_SIZE 20 #define STDIN_STR 20 int main() { //默認(rèn)打開(kāi)3個(gè)流,不需要手動(dòng)open char buffer[BUFFER_SIZE]; char* msg1stdout\n; char* msg2stderr\n; char stdin_str[STDIN_STR]; ssize_t lenread(0,stdin_str,BUFFER_SIZE); stdin_str[len]\0; write(1,stdin_str,strlen(stdin_str)); write(1,msg1,strlen(msg1)); write(2,msg2,strlen(msg2)); return 0; }運(yùn)行結(jié)果:解釋:使用read系統(tǒng)調(diào)用將顯示器文件中的字符串寫(xiě)入到stdin_str中(一開(kāi)始會(huì)阻塞,因?yàn)樵诘却脩?hù)輸入),read返回值如果大于0,那么表示實(shí)際讀取的字符數(shù),要想能打印字符串,必須先為stdin_str的結(jié)尾添加\0.然后將stdin_str的字符串寫(xiě)入到stdin中打印write(1,msg1,strlen(msg1))和write(2,msg2,strlen(msg2))都像顯示器寫(xiě)入字符串注意:雖然C語(yǔ)言的三個(gè)流是FILE類(lèi)型的,但在操作系統(tǒng)層面上只認(rèn)識(shí)fd說(shuō)明fd0是stdout,而fd1是stderr在之后的文章會(huì)證明★規(guī)則3先關(guān)閉0號(hào)文件描述符指向的struct file,再新建一個(gè)文件#include stdio.h #include unistd.h #include fcntl.h #include sys/stat.h int main() { close(0); umask(0); int fdopen(test.txt,O_WRONLY|O_CREAT|O_APPEND,0666); if (fd0) { perror(open failed); return -1; } printf(test.txt fd%d\n,fd); close(fd); return 0; }運(yùn)行結(jié)果:先關(guān)閉2號(hào)文件描述符指向的struct file,再新建一個(gè)文件#include stdio.h #include unistd.h #include fcntl.h #include sys/stat.h int main() { close(2); umask(0); int fdopen(test.txt,O_WRONLY|O_CREAT|O_APPEND,0666); if (fd0) { perror(open failed); return -1; } printf(test.txt fd%d\n,fd); close(fd); return 0; }運(yùn)行結(jié)果:從這兩段代碼可以看出: close(0),打印的是0; close(2),打印的是2…..得出規(guī)則3: 設(shè)置文件的描述符時(shí),從0下標(biāo)開(kāi)始,尋找最小的沒(méi)有用的數(shù)組位置,它的下標(biāo)就是新文件的文件描述符FILE結(jié)構(gòu)體上面提到了雖然C語(yǔ)言的三個(gè)流是FILE類(lèi)型的,但在操作系統(tǒng)層面上只認(rèn)識(shí)fd,為了讓FILE結(jié)構(gòu)體與操作系統(tǒng)交互,FILE結(jié)構(gòu)體內(nèi)部必然含有fd注意:FILE結(jié)構(gòu)體和內(nèi)核的file結(jié)構(gòu)體沒(méi)有任何關(guān)系查看gilbc-2.42的源代碼:在libio/bits/types/FILE.h中:#ifndef __FILE_defined #define __FILE_defined 1 struct _IO_FILE; /* The opaque type of streams. This is the definition used elsewhere. */ typedef struct _IO_FILE FILE; #endifFILE實(shí)際上是struct _IO_FILE的別名在libio/bits/types/struct_FILE.h中:/* The tag name of this struct is _IO_FILE to preserve historic C mangled names for functions taking FILE* arguments. That name should not be used in new code. */ struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ /* The following pointers correspond to the C streambuf protocol. */ char *_IO_read_ptr; /* Current read pointer */ char *_IO_read_end; /* End of get area. */ char *_IO_read_base; /* Start of putbackget 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; int _flags2:24; /* Fallback buffer to use when malloc fails to allocate one. */ char _short_backupbuf[1]; __off_t _old_offset; /* This used to be _offset but its too small. */ /* 1column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE };里面有一個(gè)int _fileno,這是文件描述符因?yàn)閟tdin、stdout和stderr本身是結(jié)構(gòu)體指針類(lèi)型,所以可以使用-來(lái)打印_fileno對(duì)應(yīng)的值:驗(yàn)證stdin、stdout、stderr對(duì)應(yīng)的文件描述符方法1:使用_fileno#include stdio.h int main() { printf(stdin fd%d\n,stdin-_fileno); printf(stdout fd%d\n,stdout-_fileno); printf(stderr fd%d\n,stderr-_fileno); return 0; }運(yùn)行結(jié)果:方法2: 使用fileno()當(dāng)然,也可以使用stdio.h中的fileno()內(nèi)部的宏來(lái)獲取fd值:聲明:int fileno(FILE *stream);例如:#include stdio.h int main() { printf(stdin fd%d\n,fileno(stdin)); return 0; }運(yùn)行結(jié)果:細(xì)心的讀者可能回去看glibc庫(kù)中的實(shí)現(xiàn),在glibc-2.42的/libio/fileno.c提供了__fileno的實(shí)現(xiàn):#include libioP.h #include stdio.h int __fileno (FILE *fp) { CHECK_FILE (fp, EOF); if (!(fp-_flags _IO_IS_FILEBUF) || _IO_fileno (fp) 0) { __set_errno (EBADF); return -1; } return _IO_fileno (fp); } libc_hidden_def (__fileno) weak_alias (__fileno, fileno) libc_hidden_weak (fileno) /* The fileno implementation for libio does not require locking because it only accesses once a single variable and this is already atomic (at least at thread level). Therefore we dont test _IO_MTSAFE_IO here. */ weak_alias (__fileno, fileno_unlocked)如果沒(méi)問(wèn)題的話(huà),會(huì)執(zhí)行宏_IO_fileno (fp)這個(gè)宏在/libio/iolibio.h中:#define _IO_fileno(FP) ((FP)-_fileno) extern FILE* _IO_popen (const char*, const char*) __THROW;方法3:查看/dev目錄下的文件ls -l /dev運(yùn)行結(jié)果:引用計(jì)數(shù)f_ref一個(gè)文件可以被多個(gè)進(jìn)程打開(kāi),那么該文件的struct file中專(zhuān)門(mén)有一個(gè)變量f_ref來(lái)記錄有多少個(gè)進(jìn)程打開(kāi)了這個(gè)文件,這是引用計(jì)數(shù)1. 如果有N個(gè)文件描述符指向該文件, 那么struct file的引用計(jì)數(shù)變量的大小等于N2.如果某個(gè)文件的引用計(jì)數(shù)變量值為0,那么內(nèi)核才會(huì)真正執(zhí)行關(guān)閉文件的操作,回收struct file對(duì)象,文將件指針置為NULL3.拓展閱讀linux應(yīng)用層的write()函數(shù)如何調(diào)用到驅(qū)動(dòng)中的write()函數(shù)作者: 簡(jiǎn)叔4.補(bǔ)充練習(xí)題Linux下兩個(gè)進(jìn)程可以同時(shí)打開(kāi)同一個(gè)文件,這時(shí)如下描述錯(cuò)誤的是:1.一個(gè)進(jìn)程對(duì)文件長(zhǎng)度和內(nèi)容的修改另外一個(gè)進(jìn)程可以立即感知 ( )2.任何一個(gè)進(jìn)程刪除該文件時(shí)另外一個(gè)進(jìn)程會(huì)立即出現(xiàn)讀寫(xiě)失敗 ( )答案:1.正確因?yàn)槲募?nèi)容的修改是直接反饋至磁盤(pán)文件系統(tǒng)中的,因此當(dāng)文件內(nèi)容被修改,其他進(jìn)程因?yàn)橐彩轻槍?duì)磁盤(pán)數(shù)據(jù)的操作,因此可以立即感知到可以做個(gè)測(cè)試:#include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h int main() { int fdopen(./file.txt,O_CREAT|O_WRONLY,0664); if (fd0) { perror(open failed); return 1; } char buffer[]teststring; for (;;) { if (write(fd,buffer,sizeof(buffer)-1)-1) { perror(open failed); return 2; } printf(寫(xiě)入了字符串\n); fflush(stdout); sleep(1); } close(fd); return 0; }開(kāi)兩個(gè)終端,一個(gè)執(zhí)行以上代碼,另外一個(gè)查看file.txt大小運(yùn)行結(jié)果:打印出來(lái)文件大小一直在變2.正確刪除文件實(shí)際上一開(kāi)始是刪除文件的目錄項(xiàng),文件的數(shù)據(jù)以及inode并不會(huì)立即被刪除,因?yàn)檫M(jìn)程打開(kāi)文件的struct file數(shù)據(jù)結(jié)構(gòu)仍然存在因此若進(jìn)程已經(jīng)打開(kāi)文件,文件被刪除時(shí),并不會(huì)影響進(jìn)程的操作,因?yàn)檫M(jìn)程已經(jīng)具備文件的描述信息仍然使用上面的代碼,開(kāi)兩個(gè)終端,一個(gè)執(zhí)行以上代碼,另外一個(gè)執(zhí)行rm file.txt運(yùn)行結(jié)果: 正常刪除5.補(bǔ)充: Linux的終端文件打開(kāi)xshell,打開(kāi)多個(gè)窗口,這里打開(kāi)4個(gè)終端,都以同一個(gè)用戶(hù)的身份登錄:此時(shí)查看/dev/pts下的內(nèi)容:ls -l /dev/pts注: pts全稱(chēng)是pseudo terminal slave,偽終端從設(shè)備; ptmx全稱(chēng)是pseudo terminal multiplexor. 可以看看pts(4) - Linux manual page的描述關(guān)掉其中一個(gè)終端,再查看/dev/pts中的文件:關(guān)掉其中一個(gè)終端,再查看/dev/pts中的文件:嘗試向/dev/pts/1中顯示數(shù)據(jù):結(jié)論1: /dev/pts其中一個(gè)名稱(chēng)為數(shù)字的文件就是一個(gè)終端文件關(guān)掉其中一個(gè)終端,再查看/dev/pts中的文件:結(jié)論2: 如果開(kāi)了多個(gè)終端,會(huì)在/dev/pts目錄下生成多個(gè)終端文件除此之外,可以看看stackexchange的兩個(gè)回答:linux - What is stored in /dev/pts files and can we open them? - Unix Linux Stack Exchangetty - How does a Linux terminal work? - Unix Linux Stack Exchange