凝视深渊的猪

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性能几乎被占满,说明过于频繁的系统调用,频繁地切换内核态与用户态,会消耗大量的性能。

 

 

 

 

   

 

 

 

 

 

 

  

 

  

  

posted on 2021-05-07 23:35  凝视深渊的猪  阅读(336)  评论(0编辑  收藏  举报

导航