首先感谢孟老师和李老师在教学中的辛勤付出。经过几个月的学习,我对于Linux系统的理解又加深了,特别是在汇编语言函数调用堆栈那几节课,解答了我对于系统函数调用底层机理的疑惑,算是学有所得吧。下面就来总结一下,尽可能的统摄所学的课程知识。如有纰漏,还请多多指教。

前言

首先感谢孟老师和李老师在教学中的辛勤付出。经过几个月的学习,我对于Linux系统的理解又加深了,特别是在汇编语言函数调用堆栈那几节课,解答了我对于系统函数调用底层机理的疑惑,算是学有所得吧。下面就来总结一下,尽可能的统摄所学的课程知识。如有纰漏,还请多多指教。

Linux系统概念模型

当前主流计算机是基于冯洛伊曼结构的,利用的是“存储程序”的思想,其主要的几大部件为CPU、内存、硬盘、输入和输出设备。程序存储在硬盘中,当需要运行时,就把它们传输到内存里,“分解”为指令,以“进程”为资源进行调度,调度到CPU上执行。CPU就像贪吃蛇一样,一个个的按序“吃”指令,这样就使得各种程序得以执行。上述是最简化的阐述,能够快速帮我们建立对于计算机运作的理性理解,仅仅能保证大方向不出现偏差。

如果只是知道这些是远远不够的,无法感受到Linux操作系统的精妙。举例来说,多个程序时,让哪个程序先上CPU?程序应该放在内存哪个位置?以何种方式组织?怎样快速的增删改查文件?多个设备接入时如何分配优先级等等。这些都是Linux操作系统需要考虑的事。所幸的是,前人已经设计周全了,上述的问题分别可以用进程调度、内存管理与分配、文件系统、设备管理这些设计来解决。

所以,Linux操作系统被抽象为不同的层级和模块,如下图所示。Linux内核向下管理着硬件资源,向上提供统一的系统调用供应用程序使用。
Linux操作系统分析-课程总结_操作系统

让我们把Linux系统再精简些,就是下图所示的模样:
内核直接与硬件打交道,并给上层应用提供系统调用,让它们间接的使用硬件资源。壳shell是Linux系统中方便人机交互的界面软件,库函数不属于Linux内核,但它封装了基本的功能供人使用,提高了编程效率。

Linux操作系统分析-课程总结_Linux_02

三大法宝与两把宝剑

Linux系统中的三大法宝是:存储程序,堆栈,中断。两把宝剑是:中断上下文,进程上下文。

上述概念主要是针对进程调度的,内存管理暂未深入设计,文件系统可以参考我之前的一篇博客:Linux文件系统,至于设备管理,后续我将再出一篇博客来介绍。

进程调度中一个很重要的概念是寄存器编程,当发生函数调用或中断时,Linux系统将原先的进程信息压入堆栈保存起来,将新的进程信息载入,等调用或中断完成返回时再重新恢复原先的现场。之前仅仅知道这些概念,现在看到了具体的汇编代码,通过实验二的实操,我懂得了实际的过程。

实际应用程序分析

在文件的读写中,需要用到系统调用,适度合理的系统调用可以保证读写效率和安全性,而频繁的系统调用则会降低读写效率。下面通过我之前实现的"who"命令中的代码来分析一下系统调用是如何影响系统性能的。

读写文件(不使用缓存)

//who01.c
/* copyright@lularible
*  2021/02/04
*/
#include#include#include#include#include#include//辅助函数声明
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,&current_record,reclen) == reclen){
        show_info(&current_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#include#include#include#include#include//定义缓冲区大小
#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,&current_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操作系统十分的庞大,孟老师和李老师选取了其中最重要的特性来给我们授课,就是抓住了主要矛盾,其余的相关细节可以在后续的学习中填补。再次感谢孟老师和李老师的精彩讲课!