Linux操作系统分析总结
一、系统调用
1、用户态与内核态
在用户态下,系统可执行的指令以及可访问的内存区域受到限制,内核态则与之对应,可以执行一些高级别指令。简单理解就是,某些敏感指令以及区域,放任程序员操作,可能会对系统造成伤害,这些指令只能交予操作系统内核去执行,内核程序由更专业的人编写,相对安全。
2、系统调用过程
1、用户态执行 int $0X80 或者 syscall 触发系统调用。
2、利用寄存器保存现场,其中EAX中存放系统调用号。
3、CPU切换到内核态执行system_call(32or64)汇编代码,此时需要从EAX寄存器中获取系统调用号,内核才能知道执行哪个系统调用。
4、执行完后恢复现场。
5、系统调用返回。
二、中断
1、中断分类
中断分为外部(硬件)与内部(软件),内部中断又叫异常,异常分为故障(如缺页)和陷阱(如系统调用)。
中断还分为可屏蔽和不可屏蔽。
2、中断流程
1、硬件控制设备的IRQ线通过中断控制器的输入引脚发送中断请求信号。
2、中断控制器把信号转换成对应的中断向量,把该向量放在I/O端口,待CPU读。
3、中断控制器发送引发信号给处理器的INTR引脚,产生中断,随后等待CPU应答。
4、CPU取到中断向量值,随后根据idtr寄存器的值找到IDT(中断描述符表),IDT表里存放着每个中断对应的处理程序入口地址。
5、CPU从gdtr寄存器获取GDT(全局描述符表)基地址,从GDT表中找到IDT表里段选择符所标识的段描述符。
6、确认授权。
7、保存eflags,cs,eip等内容。
8、执行中断处理程序。
9、还原现场并返回。
三、进程
1、进程的组成
进程由代码段、用户数据段、还有系统数据段(存放PCB)组成。
进程被创建时就建立一个PCB,PCB中的pid唯一标识一个进程。
2、进程的状态
3、进程调度算法
1、非剥夺(给了CPU资源,一直到它执行完):先来先服务。
2、剥夺(未执行完,但在某些条件下CPU资源被剥夺给其他进程):优先级、短进程、时间片。
4、进程调度时机
1、进程状态变化
2、进程时间片用完
3、进程从中断处理后返回用户态
4、调用schedule函数时
四、文件系统
1、文件分类
正规文件(系统文件、库文件、用户文件等)、目录文件、符号链接(软链接,是快捷方式)、设备文件、管道文件(Pipe)、套接字(Socket)。
2、文件描述符
即FCB(文件控制块),在Linux中为inode,包含inode号、文件类型、硬链接个数、文件长度、拥有者UID、访问权限、时间戳等属性。
通过文件目录中的文件名,可以找到对应的FCB,所以文件是按名存取的,将文件目录以文件形式保存,就是目录文件。
3、文件读写
通过读写指针维护读写位置。
read(文件名,内存位置,记录键)表示把文件名所指定文件中的指定键值读入到某内存位置。
write(文件名,内存位置,记录键)表示把某内存位置的值作为指定键值的一个记录写入到文件中。
4、文件系统结构
引导控制块(启动)、盘控制块(管理资源,超级块)、目录结构、FCB。
在内存中则包括系统文件打开表和进程打开文件表。
5、VFS(虚拟文件系统)
VFS是一种通用文件模型,包含两个接口,一个与用户连接,一个与底层的文件系统层(提供具体的文件结构实现)连接。
不同文件系统通过mount(挂载、安装)到根文件系统中。
用户通过VFS提供的read(),write()等系统调用操作文件,VFS调用sys_read()、sys_write()。
VFS由以下对象组成:超级块对象(存放文件系统信息)、inode对象(FCB)、文件对象(已打开文件和进程的交互信息)、目录项对象(目录与文件的链接信息)。
五、程序装载
1、程序编译过程
预处理——>编译——>汇编——>链接。
预处理:删除注释、处理预编译指令,添加行号和文件标识。
编译:检查代码规范性,把代码翻译成汇编语言。
汇编:当汇编语言转化为机器认识的机器码。
链接:将不同部分代码和数据组合成一个可执行文件,链接可分为编译时的静态链接,以及加载时的动态链接。
2、静态链接
分为符号解析和重定位两步。
符号解析就是把每个符号的引用和符号定义关联起来,维护一个符号表来记录这些符号的位置。
重定位就是将这些符号的引用指向一个具体的内存位置。
它的缺点是,占用空间有点多,比如库中有个调用比较平凡的函数,采用静态链接,但这段函数代码可能会出现很多次。
3、动态链接
编译的时候只记录符号和参数,等到程序运行时,再链接,就是操作系统先将动态库加载进内存,等程序运行的时候,再去库中找相对应的代码。
它的好处时,提高了代码的共享度。
4、fork()和execve()
fork()的作用是创建一个进程号是0的子进程,他从创建的一刻起和父进程执行一样的程序。
execve()就是执行程序,比如在shell中去执行test程序,shell就会fork()一个子进程,这个子进程调用execve()方法去执行这个test程序。
六、案例分析
在控制台输入一个简单的ls命令,按下键盘的时候,管理键盘的硬件控制设备向中断控制器发送中断请求,中断控制器根据发来的请求生成中断向量号告诉cpu,cpu会根据中断向量号执行中断处理程序,在程序里,会向屏幕打印字符,所以我们输入的命令出现在了屏幕上。
之后Shell会fork一个子进程执行,fork是一个系统调用,执行系统调用的过程类似中断处理的过程。fork后出创建一个子进程,子进程创建后处于Runable状态,此时他等待处理机给他分配资源,进程给他分配资源后,这里就进行了上述的进程调度过程。
由于子进程是复制过来的,所以他其实也是一个shell程序,他会执行exec系统调用,并将ls命令的可执行文件装入到内存里面,之后执行该命令。
最后,从exec系统调用中返回。
七、实际程序运行分析
1、CPU性能分析
下图为刚打开firefox浏览器时,CPU占用情况,用top指令查看,可以看到firefox占用了超过50%的CPU资源。
第二张图为过了一段时间后,发现占用CPU资源下降到了12%,这是因为,程序刚启动的时候,一些初始化操作,进程的切换,占用了大量的资源。
这表明,在平时使用操作系统时,应尽量避免程序的频繁重启。
2、IO性能分析
使用iostat指令查看IO性能情况,我们可以看到各个设备的读写速度。通过判断%iowait是否过高,可以分析是否存在IO瓶颈。
此外%idle表示cpu的空闲率,该值高达90%,说明此时空闲资源较多。
3、内存占用分析
用top命令除了可以查看cpu占用情况,也可以看到内存占用情况,在第1节中我们可以通过进程后面的%mem查看该进程的内存占用率。
此外,还可以结合free命令来直观地分析内存性能。我们同样利用firefox程序来测试。
上图中,是我们刚启动firefox过了一段时间后,占用情况。下图为我们在firefox中使用一段时间,随着窗口打开越多,浏览信息越多后重新用free和top命令查看的内存占用情况。
我们可以发现,空闲内存显著变低,同时出现了大量web content进程占用了大量内存。
这说明随着程序使用时间变长,如果不加清理,可能会消耗越来越多的内存空间。
4、进程执行分析
我们可以在执行的程序之前加上strace,来追踪程序执行。
上图中,我们用strace来追钟bomb程序的执行,通过strace输出的信息,我们可以看到shell是通过调用execve函数来执行程序,进程PID为7022,随后利用write()系统调用输出了该程序的提示信息,通过提示信息我们知道该程序需要输入6个字符串来闯关。之后便调用read()调用来读取用户的输入内容。
当输入错误的答案后,进程通过write()系统调用打印出错信息,之后通过exit_group()系统调用退出,这里的exit_group()指的是终结该进程的所有线程。
接下来我们用strace追踪一个无限循环写入操作的程序
我们用strace追踪,并结合top指令查看性能。
通过上图可以发现,该进程不停地调用write()系统调用向文件里写入内容,而此时CPU性能几乎被占满,说明过于频繁的系统调用,频繁地切换内核态与用户态,会消耗大量的性能。