1.为什么读写文件要先open()?
看下面代码,通常我们读写文件,都是要先open,然后读写,处理完业务之后还有close一下。
fd = open(pathname, flags, mode); rlen = read(fd, buf, count); wlen = write(fd, buf, count); close(fd); |
为什么读写文件都要先open()一下,读写完又要close()呢?
以前老师只告诉我们要open和一定一定要close,有时候我们忘记close代码也不报错,一样正常运行。
为什么?希望我们带着好奇心,往下寻找答案
2.要理解文件描述符fd,要从启动进程开始
先看图,网友画的特别好,借用一下
(1)启动进程,系统会创建进程控制块来存储进程信息,task_struct,产生一个pid,叫进程描述符。
struct task_struct {
//打开文件信息
struct files_struct *files;
volatile long state; //说明了该进程是否可以执行,还是可中断等信息
unsigned long flags; //Flage 是进程号,在调用fork()时给出
int sigpending; //进程上是否有待处理的信号
mm_segment_t addr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
//0-0xBFFFFFFF for user-thead
//0-0xFFFFFFFF for kernel-thread
//调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
volatile long need_resched;
int lock_depth; //锁深度
long nice; //进程的基本时间片
//进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER
unsigned long policy;
struct mm_struct *mm; //进程内存管理信息
int processor;
//若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新
unsigned long cpus_runnable, cpus_allowed;
struct list_head run_list; //指向运行队列的指针
unsigned long sleep_time; //进程的睡眠时间
//用于将系统中所有的进程连成一个双向循环链表, 其根是init_task
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm;
struct list_head local_pages; //指向本地页面
unsigned int allocation_order, nr_local_pages;
struct linux_binfmt *binfmt; //进程所运行的可执行文件的格式
int exit_code, exit_signal;
int pdeath_signal; //父进程终止时向子进程发送的信号
unsigned long personality;
//Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
int did_exec:1;
pid_t pid; //进程标识符,用来代表一个进程
pid_t pgrp; //进程组标识,表示进程所属的进程组
pid_t tty_old_pgrp; //进程控制终端所在的组标识
pid_t session; //进程的会话标识
pid_t tgid;
int leader; //表示进程是否为会话主管
struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
struct list_head thread_group; //线程链表
struct task_struct *pidhash_next; //用于将进程链入HASH表
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit; //供wait4()使用
struct completion *vfork_done; //供vfork() 使用
unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值
//it_real_value,it_real_incr用于REAL定时器,单位为jiffies, 系统根据it_real_value
//设置定时器的第一个终止时间. 在定时器到期时,向进程发送SIGALRM信号,同时根据
//it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。
//当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送
//信号SIGPROF,并根据it_prof_incr重置时间.
//it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种
//状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据
//it_virt_incr重置初值。
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_value;
struct timer_list real_timer; //指向实时定时器的指针
struct tms times; //记录进程消耗的时间
unsigned long start_time; //进程创建的时间
//记录进程在每个CPU上所消耗的用户态时间和核心态时间
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS];
//内存缺页和交换信息:
//min_flt, maj_flt累计进程的次缺页数(Copy on Write页和匿名页)和主缺页数(从映射文件或交换
//设备读入的页面数); nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
//cmin_flt, cmaj_flt, cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
//在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1; //表示进程的虚拟地址空间是否允许换出
//进程认证信息
//uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid
//euid,egid为有效uid,gid
//fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件
//系统的访问权限时使用他们。
//suid,sgid为备份uid,gid
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups; //记录进程在多少个用户组中
gid_t groups[NGROUPS]; //记录进程所在的组
//进程的权能,分别是有效位集合,继承位集合,允许位集合
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
int keep_capabilities:1;
struct user_struct *user;
struct rlimit rlim[RLIM_NLIMITS]; //与进程相关的资源限制信息
unsigned short used_math; //是否使用FPU
char comm[16]; //进程正在运行的可执行文件名
//文件系统信息
int link_count, total_link_count;
//NULL if no tty 进程所在的控制终端,如果不需要控制终端,则该指针为空
struct tty_struct *tty;
unsigned int locks;
//进程间通信信息
struct sem_undo *semundo; //进程在信号灯上的所有undo操作
struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
//进程的CPU状态,切换时,要保存到停止进程的task_struct中
struct thread_struct thread;
//文件系统信息
struct fs_struct *fs;
//信号处理函数
spinlock_t sigmask_lock;
struct signal_struct *sig; //信号处理函数
sigset_t blocked; //进程当前要阻塞的信号,每个信号对应一位
struct sigpending pending; //进程上是否有待处理的信号
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
u32 parent_exec_id;
u32 self_exec_id;
spinlock_t alloc_lock;
void *journal_info;
}
struct files_struct {
int next_fd;
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
atomic_t count;
spinlock_t file_lock ____cacheline_aligned_in_smp;
struct embedded_fd_set close_on_exec_init;
struct embedded_fd_set open_fds_init;
struct address_space *i_mapping;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
unsigned long i_ino;//inode号
union {
const unsigned int i_nlink;//硬链接个数
unsigned int __i_nlink;
};
dev_t i_rdev;//如果inode代表设备,i_rdev表示该设备的设备号
loff_t i_size;//文件大小
struct timespec i_atime;//最近一次访问文件的时间
struct timespec i_mtime;//最近一次修改文件的时间
struct timespec i_ctime;//最近一次修改inode的时间
spinlock_t i_lock;
unsigned short i_bytes;//文件中位于最后一个块的字节数
unsigned int i_blkbits;//以bit为单位的块的大小
blkcnt_t i_blocks;//文件使用块的数目
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;//对i_size进行串行计数
#endif
unsigned long i_state;//inode状态,可以是I_NEW,I_LOCK,I_FREEING等
struct mutex i_mutex;//保护inode的互斥锁
//inode第一次为脏的时间 以jiffies为单位
unsigned long dirtied_when;
struct hlist_node i_hash;//散列表
struct list_head i_wb_list;
struct list_head i_lru;
struct list_head i_sb_list;//超级块链表
union {
struct hlist_head i_dentry;//所有引用该inode的目录项形成的链表
struct rcu_head i_rcu;
};
u64 i_version;//版本号 inode每次修改后递增
atomic_t i_count;//引用计数
atomic_t i_dio_count;
atomic_t i_writecount;//记录有多少个进程以可写的方式打开此文件
const struct file_operations *i_fop;
struct file_lock *i_flock;//文件锁链表
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];//inode磁盘限额
#endif
struct list_head i_devices;,
union {
struct pipe_inode_info *i_pipe;//如果文件是一个管道则使用i_pipe
struct block_device *i_bdev;//如果文件是一个块设备则使用i_bdev
struct cdev *i_cdev;//如果文件是一个字符设备这使用i_cdev
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
//目录通知事件掩码
__u32 i_fsnotify_mask;
struct hlist_head i_fsnotify_marks;
#endif
#ifdef CONFIG_IMA
atomic_t i_readcount;
#endif
//存储文件系统或者设备的私有信息
void *i_private;
}
3、一个文件一个inode结构体,里面对应一个系统唯一inode号
struct inode的结构体就是存储文件属性的结构体。注意inode只是存储文件的属性,文件属性和文件存储的内容是分开存储的。
硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。
inode结构体很多数据,有兴趣可以详细研读,我们现在只关心(unsigned long i_ino;//inode号):一个文件一个inode结构体,里面对应一个系统唯一inode号。
4、查看文件inode信息
#stat rongshu.txt
file:文件名
size:大小
blocks:文件使用的数据块总数 8
io blocks:IO块大小4096
device:fd01h/64769d:磁盘位置
inode:786486:索引节点(这里看到每个文件都有一个inode号)
link:链接数,即有多少文件名指向这个inode
5、fd的node和inode struct里面的inode
细心的你可能发现,fd的node和inode struct里面的inode都是一个数字,名字也是类似,那是不是同一个东西?答案:是的,是同一个东西,可以自己验证
#exec 8< io.txt
#lsof -p $$
#stat io.txt
6.inode大小
(1)#df -i
(2)一般是128字节或256字节
7.inode是VFS使用的一个对象
(1)刚有说到,inode只是文件的属性,和内容是分开的,实际上inode是VFS使用的一个对象,是linux虚拟文件系统,挂载磁盘真实文件而创建的一个东西。
索引节点有两种:
一种是这里所说的VFS索引节点,存在内存中
一种是具体文件系统的索引节点,存在于磁盘上(可以理解为文件存储块的起始位置)
(2)打开一个文件时,操作系统就会把对应的文件的inode的部分属性摘录下来放在内存里,存在fd_array中
fd_array,可以理解是个二维数组,key=fd,value=node,node是inode号,这时返回个fd给用户,就可以操作文件了
(3)操作文件的时候,就可以快速通过fd找到node(inode号),通过inode号可以找到文件具体位置
三、了解fd有啥用最后了解文件描述符有啥用呢?我理解
1.我们要做系统性能优化,I/O就是很重要的一块,I/O又分文件I/O,网络I/O。要想理解操作系统I/O工作原理,文件描述符是第一个要掌握的。这里是为后面学习I/O知识打基础。
2.理解这个更好理解操作系统抽象统一的思想,vfs就是个很好的例子,万物可以挂载。内核通过fd就可以高效管理系统资源
3.扩展:操作文件调用open可以拿到一个fd,启动服务调用socket函数可以拿到一个fd,进程间通信调用pipe可以拿到2个fd。
关于I/O学习是很重要的,文件描述符就写到这。



