前言
首先感谢孟老师和李老师在教学中的辛勤付出。经过几个月的学习,我对于Linux系统的理解又加深了,特别是在汇编语言函数调用堆栈那几节课,解答了我对于系统函数调用底层机理的疑惑,算是学有所得吧。下面就来总结一下,尽可能的统摄所学的课程知识。如有纰漏,还请多多指教。
Linux系统概念模型
当前主流计算机是基于冯洛伊曼结构的,利用的是“存储程序”的思想,其主要的几大部件为CPU、内存、硬盘、输入和输出设备。程序存储在硬盘中,当需要运行时,就把它们传输到内存里,“分解”为指令,以“进程”为资源进行调度,调度到CPU上执行。CPU就像贪吃蛇一样,一个个的按序“吃”指令,这样就使得各种程序得以执行。上述是最简化的阐述,能够快速帮我们建立对于计算机运作的理性理解,仅仅能保证大方向不出现偏差。
如果只是知道这些是远远不够的,无法感受到Linux操作系统的精妙。举例来说,多个程序时,让哪个程序先上CPU?程序应该放在内存哪个位置?以何种方式组织?怎样快速的增删改查文件?多个设备接入时如何分配优先级等等。这些都是Linux操作系统需要考虑的事。所幸的是,前人已经设计周全了,上述的问题分别可以用进程调度、内存管理与分配、文件系统、设备管理这些设计来解决。
所以,Linux操作系统被抽象为不同的层级和模块,如下图所示。Linux内核向下管理着硬件资源,向上提供统一的系统调用供应用程序使用。
让我们把Linux系统再精简些,就是下图所示的模样:
内核直接与硬件打交道,并给上层应用提供系统调用,让它们间接的使用硬件资源。壳shell是Linux系统中方便人机交互的界面软件,库函数不属于Linux内核,但它封装了基本的功能供人使用,提高了编程效率。
三大法宝与两把宝剑
Linux系统中的三大法宝是:存储程序,堆栈,中断。两把宝剑是:中断上下文,进程上下文。
上述概念主要是针对进程调度的,内存管理暂未深入设计,文件系统可以参考我之前的一篇博客:Linux文件系统,至于设备管理,后续我将再出一篇博客来介绍。
进程调度中一个很重要的概念是寄存器编程,当发生函数调用或中断时,Linux系统将原先的进程信息压入堆栈保存起来,将新的进程信息载入,等调用或中断完成返回时再重新恢复原先的现场。之前仅仅知道这些概念,现在看到了具体的汇编代码,通过实验二的实操,我懂得了实际的过程。
实际应用程序分析
在文件的读写中,需要用到中断,适度合理的中断可以保证读写效率和安全性,而频繁的中断则会降低读写效率。下面通过我之前实现的"who"命令中的代码来分析一下中断是如何影响系统性能的。
读写文件(不使用缓存)
//who01.c
/* copyright@lularible
* 2021/02/04
*/
#include<stdio.h>
#include<utmp.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
//辅助函数声明
void show_info(struct utmp*);
void showtime(long);
int main()
{
struct utmp current_record; //定义utmp结构体
int utmpfd; //定义文件描述符
int reclen = sizeof(current_record); //获得utmp结构体大小
//打开文件的错误处理
if((utmpfd = open(UTMP_FILE,O_RDONLY)) == -1){
perror(UTMP_FILE);
exit(1);
}
//读取文件内容并打印
while(read(utmpfd,¤t_record,reclen) == reclen){
show_info(¤t_record);
}
close(utmpfd);
return 0;
}
//打印读取到的结构体数据
void show_info(struct utmp* utbufp)
{
//当目前记录不是用户信息时,舍弃
if(utbufp->ut_type != USER_PROCESS){
return;
}
//打印用户名
printf("%-10.10s",utbufp->ut_name);
printf(" ");
//打印用户登录终端
printf("%-10.10s",utbufp->ut_line);
printf(" ");
//打印用户登录时间
showtime((long)utbufp->ut_time);
//打印用户登录地址
if(utbufp->ut_host[0] != ‘\0‘){
printf("(%s)",utbufp->ut_host);
}
printf("\n");
}
//完成时间转换并打印
void showtime(long timeval)
{
char *cp;
cp = ctime(&timeval);
printf("%24.24s",cp);
}
运行次数最多的系统调用就是read函数(登录的用户记录可能有很多条,需要多次调用read读取),关键点就在于一次只读取了一个utmp结构体大小的数据。如果一次能够读取多条结构体数据,缓存起来,然后从缓冲中拿就行,缓冲中拿完了再调用read,这样就能减少系统调用次数,提高系统效率。
读写文件(使用缓存)
//who2.c
/* copyright@lularible
* 2021/02/04
*/
#include<stdio.h>
#include<utmp.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
//定义缓冲区大小
#define ITEMS 8
//辅助函数声明
void show_info(struct utmp*);
void showtime(long);
int main()
{
struct utmp current_record; //utmp结构体
struct utmp* next_record; //缓冲区中下一个要拿的结构体
int utmpfd; //文件描述符
int records_size = 0; //read一次读取的内容大小
int records_cnt = 0; //read一次读取的结构体个数
int reclen = sizeof(current_record); //获得utmp结构体大小
char utmpbuf[reclen * ITEMS]; //缓冲区
//打开文件的错误处理
if((utmpfd = open(UTMP_FILE,O_RDONLY)) == -1){
perror(UTMP_FILE);
exit(1);
}
//读取文件内容并打印
while(read(utmpfd,¤t_record,reclen) == reclen){
//一次读取了多少个utmp结构体
records_cnt = records_size / reclen;
int i = 0;
//从缓冲区中拿数据并打印
for(i = 0;i < records_cnt;++i){
next_record = (struct utmp*) &utmpbuf[i * reclen];
show_info(next_record);
}
}
close(utmpfd);
return 0;
}
//打印读取到的结构体数据
void show_info(struct utmp* utbufp)
{
//当目前记录不是用户信息时,舍弃
if(utbufp->ut_type != USER_PROCESS){
return;
}
//打印用户名
printf("%-10.10s",utbufp->ut_name);
printf(" ");
//打印用户登录终端
printf("%-10.10s",utbufp->ut_line);
printf(" ");
//打印用户登录时间
showtime((long)utbufp->ut_time);
//打印用户登录地址
if(utbufp->ut_host[0] != ‘\0‘){
printf("(%s)",utbufp->ut_host);
}
printf("\n");
}
//完成时间转换并打印
void showtime(long timeval)
{
char *cp;
cp = ctime(&timeval);
printf("%24.24s",cp);
}
上述应用程序就是以中断作为切入点,通过引入缓冲技术,减少中断次数,以达到提高系统性能的目的。
总结
Linux操作系统十分的庞大,孟老师和李老师选取了其中最重要的特性来给我们授课,就是抓住了主要矛盾,其余的相关细节可以在后续的学习中填补。再次感谢孟老师和李老师的精彩讲课!