linux操作系统上的一切操作都是进程对文件的操作
上面那句话的关键词是进程和文件操作,下面我们对这两个主题进行总结
一、进程
CPU上要么在执行一个进程,要么在发生进程上下文切换或中断上下文切换。进程执行时,我们关注对文件的操作,这将会在下一节进行讨论;本节我们讨论上下文切换。
1.进程函数调用栈
- 每个用户态进程都有两个函数调用栈,一个用户态函数调用栈和一个内核态函数调用栈;
- 中断没有独立的函数调用栈,中断会占有当前被中断的进程的内核栈来作为自己的函数调用栈。
2.中断
中断发生有两种情形。一种是进程处于用户态时被中断,另一种是在内核态中发生的中断嵌套。
第一种情形,需要将用户进程的上下文按照结构体 pt_regs 的格式保存在用户的内核栈中。步骤如下:
- 硬件自动保存 ss, esp, eflags, cs 和 eip 这5个值;
- 保存硬件出错码,如果没有硬件出错码的话会保存数值0;
- 进入中断服务程序后,由 SAVE_ALL 指令保存 pt_regs 结构体中其余的值;
- 中断处理完后,SAVE_ALL 指令保存的值由 RESTORE_ALL指令恢复,硬件保存的值由硬件恢复。
第二种情形的处理与第一种情形基本类似,唯一的区别是硬件无需保存 ss 和 esp 的值。第一种情形中断前后发生了栈的切换,所以要保存着两个值;而第二种情形中断前后一直处于内核栈中,所以无需保存这两个值。
3.进程调度和切换
进程进行切换时,保存当前进程现场的工作同中断的第一种情形。
内核会维护一个进程队列和一个可运行进程队列,每次调度都会从可运行队列中选择。
可运行队列又分为 active 队列和 expired 队列。当 active 队列不为空时,expired 队列中的进程没有机会被调度;当 active 队列为空时,active 队列和 expired 队列会互换。
linux按进程优先级对进程进行调度,进程优先级会动态变化。但是进程又分为实时进程和普通进程,实时进程的优先级永远高于普通进程,而且实时进程和普通进程之间隔着一道墙,两者不会互相转化。
实时进程被切换出去时不会进入过期队列。普通进程分交互式进程和非交互式普通进程,非交互普通进程被切换出去时会进入 expired 队列,交互式进程切换出去时,一般会进入 active 队列,但是当 expired 队列中的最高优先级比当前进程高,或者 expired 队列中的最古老进程等待时间超出某个阈值时,当前交互式进程会进入 expired 队列。
进程切换的实际总结如下:
- 进程状态发生变化时
处于运行态下的进程要等待某种资源
运行态下的进程转入僵死态
等待态的进程被唤醒后加入到可运行队列中
运行态转入暂停态
暂停态转入可运行态 - 当前进程时间片用完时
- 进程从系统调用返回到用户态时
- 中断处理后,进程返回到用户态时
二、文件操作
linux对文件的操作进行了统一抽象,向上提供open, read, write, close等统一接口,这些统一接口会去找与特定文件相关的,存储在file_opration中的相关操作函数完成动作。
下面我们从相关结构体定义开始来进行深入分析。
相关结构体:
- struct file_oprations: file_oprations结构体中都是一些函数指针。由于不同文件系统、不同设备文件(如块设备文件、字符设备文件)的读、写等操作都不同,所以每种类型的文件都要有相应的特定读写函数,这些函数的入口地址(函数指针变量)就存储在结构体file_oprations中。linux提供的统一接口最终调用的就是存储在file_oprations中的相应函数。
- struct inode: 内核用一个inode结构体变量来表示一个存储在磁盘上的文件。
- struct file: 一个file结构体变量表示一个在内存中打开的文件,系统会维护由所有file结构体变量串成的链表,称为系统打开文件表;进程会维护一个file*数组,称为进程打开文件表。一个文件只要在磁盘上,就会有一个inode变量与之对应;但是该文件只有被打开时,才会有一个file变量与之对应,而且当该文件关闭时,相应的file变量就会被释放。
从进程控制块 task_struct 开始,相应结构体变量的嵌套关系总结如下图,图中花括号右边的结构体变量嵌套在花括号左边的结构体变量中。
进程对文件的操作可以总结如下:
- 进程通过进程控制块中的结构体变量 fs 可以在文件系统中定位到文件的位置;
- 进程调用 open() 接口打开一个文件。打开文件会创建一个 file 结构体变量,并初始化 file 结构体中的 file_oprations 变量,也就是对文件的操作函数。创建完后会在数组 fd_array 中寻找下标最小的空槽,令其指向新创建的 file 变量,并返回这个槽的下标,也就是我们通常说的整型变量文件描述符(fd);
- 进程调用 read() 接口对文件进行读操作。read() 调用 sys_read(),sys_read()根据文件描述符参数 fd 在进程控制块的 fd_array 中找到表示打开文件的 file 变量,并调用 file 变量中的 file_oprations 变量里的相应读函数对文件进行读操作。写操作类似,不在赘述;
- 进程调用 close() 接口关闭文件,并释放 file 变量。
下面给出一些代码片段,实际上就是上面终结的那张图的代码形式。
struct task_struct {
...
/* Filesystem information: */
struct fs_struct *fs;
/* Open file information: */
struct files_struct *files;
...
};
/*
* Open file table structure
*/
struct files_struct {
...
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
struct file {
...
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
...
} __randomize_layout
__attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;