前言
1、文件描述符是一个int型整数
2、文件描述符创建是有一定的规则。在创建文件描述符的时候系统会在files_struct数组中,去找到一个当前没有被使用的一个最小下标,作为一个新的文件描述符。
3、程序开始运行时,有三个文件被自动打开了,打开时分别使用了这三个文件描述符
- 0:标准输入
- 1:标准输出
- 2:标准错误输出
4、同一个程序中,两次打开同样的文件,返回的描述符会不相同。
5、fork()进程的时候,进程的文件描述符也会被复制
1、文件描述符表、文件表、i-node table
在理解文件描述符表、文件表、v-node表的关系之前,知道它们存放的位置至关重要~
- 每个进程都有一个属于自己的文件描述符表。
- 文件表存放在内核空间,由系统里的所有进程共享。
- 索引结点表也存放在内核空间,由所有进程所共享。
文件描述符表:该表记录进程打开的文件。它的表项里面有一个指针,指向存放在内核空间的文件表中的一个表项。它向用户提供一个简单的文件描述符(文件描述符表的下标),使得用户可以通过方便地访问一个文件。
当进程使用open打开一个文件时,内核就会在这个表中添加一个表项。如果对同一个文件打开多次,那么将有多个表项。使用dup时,也会增加一个表项,后面都会讲到。
说到现在,那到底什么是文件描述符呢?打开文件后,进程得到的文件描述符实质上就是文件描述符表的下标,内核根据这个下标值去访问相应的文件对象,从而实现对文件的操作。
文件表:文件表保存了进程对文件读写的偏移量。该表还保存了进程对文件的存取权限。比如,进程以O_RDONLY方式打开文件,这将记录到对应的文件表表项中。每一个打开的文件都对应于一个file结构体(可以理解为file对象)。file对象有引用计数short f_count,记录了引用这个对象的文件描述符个数,只有当引用计数为0时,内核才销毁file对象,因此某个进程关闭文件,不影响与之共享同一个file对象的进程。
每个文件都有一个32位的数字来表示下一个读写的字节位置,这个数字叫做文件位置(f_pos)。每次打开一个文件,除非明确要求,否则文件位置都被置为0,即文件的开始处,此后的读或写操作都将从文件的开始处执行,但你可以通过执行系统调用lessk函数对这个文件位置进行修改。
struct file
{
struct list_head f_list; /*所有打开的文件形成一个链表*/
struct dentry *f_dentry; /*指向相关目录项的指针*/
struct vfsmount *f_vfsmnt; /*指向VFS安装点的指针*/
struct file_operations *f_op; /*指向文件操作表的指针*/
mode_t f_mode; /*文件的打开模式*/
loff_t f_pos; /*文件的当前位置*/
unsigned short f_flags; /*打开文件时所指定的标志*/
unsigned short f_count; /*使用该结构的进程数*/
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
/*预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及预读的页面数*/
int f_owner; /* 通过信号进行异步I/O数据的传送*/
unsigned int f_uid, f_gid; /*用户的UID和GID*/
int f_error; /*网络写操作的错误码*/
unsigned long f_version; /*版本号*/
void *private_data; /* tty驱动程序所需 */
};
v-node/I-node 表(索引节点表):同文件表一样,所有的进程共享这张v-node表。每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员等。
struct inode {
/* RCU path lookup touches following: */
umode_t i_mode; //权限
uid_t i_uid; //用户id
gid_t i_gid; //组id
const struct inode_operations *i_op;
struct super_block *i_sb;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned int i_flags;
struct mutex i_mutex;
unsigned long i_state;
unsigned long dirtied_when; /* jiffies of first dirtying */
struct hlist_node i_hash;
struct list_head i_wb_list; /* backing dev IO list */
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
union {
struct list_head i_dentry;
struct rcu_head i_rcu;
};
unsigned long i_ino; //inode节点号
atomic_t i_count;
unsigned int i_nlink;
dev_t i_rdev;
unsigned int i_blkbits;
u64 i_version;
loff_t i_size; //文件大小
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
struct timespec i_atime; //最后一次访问(access)的时间
struct timespec i_mtime; //最后一次修改(modify)的时间
struct timespec i_ctime; //最后一次改变(change)的时间
blkcnt_t i_blocks; //块数
unsigned short i_bytes;
struct rw_semaphore i_alloc_sem;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock *i_flock;
struct address_space *i_mapping; //块地址映射
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
atomic_t i_writecount;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
void *i_private; /* fs or device private pointer */
};
2、常见的编程误区
2.1 父进程使用fork创建子进程
当程序调用 fork() 函数时, 则会出现(不同进程的)多个描述符对应于同一个文件表表项的情况。子进程在创建时会拷贝父进程的打开文件描述符表,因此父子进程是共享文件表项的。
2.2 同一个进程多次打开同一个文件
每打开一次同一个文件,内核就会在文件表中增加一个表项。这是因为每次open文件时使用了不同的读写权限,而读写权限是保存在文件表表项里面的。
2.3 使用dup函数复制一个文件描述符
dup函数是用来复制一个文件描述符的。复制得到的文件描述符和原描述符虽然数字不同,但是共享文件偏移量和一些状态(共享文件表项)。所以dup的作用仅仅是复制一个文件描述符表项,而不会复制一个文件表表项。