浅谈linux系统的机制

时间:2022-08-09 05:45:56

浅谈linux系统的机制

摘要: 随着时下的发展,操作系统也是一种人机交互的实质。根据 Linux系统工作基础的分析,对存储程序计算机、堆栈(函数调用堆栈)机制和中断机制进行概述。文中将为您提供操作系统(内核)如何工作的细节,进一步从宏观概述结合关键点进行微观分析。Linux是一种自由和开放源码的类Unix os,存在着许多不同的Linux版本,但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、视频游戏控制台、台式计算机、大型机和超级计算机。Linux是一个领先的操作系统,世界上运算最快的10台超级计算机运行的都是Linux操作系统。严格来讲,Linux这个词本身只表示Linux内核,但实际上人们已经习惯了用Linux来形容整个基于Linux内核,并且使用GNU 工程各种工具和数据库的操作系统。

Abstract: With the development, the operating system becomes a real human-computer interaction. Depending on the analysis of working base of Linux system, the stored program computer, stack (function call stack) mechanism and interrupt mechanisms are overviewed. This paper will provide the details of operating system (kernel) working, and further make microscopic analysis from macro overview and with key points. Linux is a class Unix os with free and open source, and there are many different Linux versions, but they all use the Linux kernel. Linux can be installed on a variety of computer hardware devices, such as mobile phones, tablet computers, routers, video game consoles, desktop computers, mainframes and supercomputers. Linux is a leading operating system and the world's fastest 10 supercomputers are running with the Linux operating system. Strictly speaking, Linux only means the Linux kernel, but in fact it has been used to describe the entire Linux kernel and use a variety of tools and database of GNU project.

关键词: linux;unix;系统

Key words: linux;unix;system

中图分类号:TP316 文献标识码:A 文章编号:1006-4311(2013)23-0191-03

1 存储计算机

首先让我们了解一下,什么是存储程序计算机,并对存储程序计算机的整个运行过程及所需的硬件组件进行简单介绍。

图1是程序存储计算机的物理框架,主要包含CPU(包含各类寄存器,如程序寄存器,指令寄存器等),主存,I/O设备,一个最简单的的程序存储计算机只需要以下部件来完成计算机工作:主存,也就是我们普通PC上内存,用于存储指令和数据处理器,用于执行算术和逻辑操作控制单元,解析需要操作的指令集。程序存储在计算机主存当中,并以数据的形式被CPU访问和读写,程序中各条指令都被获取并放到一个EIP寄存器,EIP寄存器中数据控制整个处理单元的运行,取“下一条”指令,继续运行。

在Linux系统中,一般同时会有几个程序一起运行,运行过程中这些程序的都存储在主存中,而CPU只会在同一时间内运行其中优先级较高的某一个,并根据优先级顺序不断的切换多个进程运行,使得计算机操作者会有多个程序同时运行的错觉。在存储程序计算机中,最重要的部分就是多个进程的切换,是什么控制着进程间的切换,如何保证进程切换过程中能够使得多个进程运行时不发生混乱,这一切都是由Linux内核控制的,下面我们深入解Linux内核的在进程切换时的工作机制。先看调度的方式。由于调度时机发生时进程在进入了内核态这样,内核必须等待该进程即将结束内核态时才进行切换操作,而进程如果正在用户态时则切换工作会立即执行,所以,一般进程调度发生在当前进程从内核态(包括从系统调用而进入内核态)返回用户态的前夕。至于调度的政策,均按照前面所提到的以优先级为基础的调度。针对不同的进程有不同的调度政策,主要有SCHED_FIFO,SCHED_RR, SCHED_OTHER(源码集中在kernel\sched目录下),其中FIFO适用于时间性要求比较高的进程,而RR针对时间片耗尽的进程,由于没有研究过源码这里不做详细描述。当切换进程已经选好后,就开始用户虚拟空间的处理,然后就是进程的切换switch_to()。所谓进程的切换主要就是堆栈的切换,这是由宏操作switch_to()完成的。

2 分析linux机制的核心

在核心这里的输出部分有三个参数,表示这段程序执行后有三项数据会有改变。其中[prev_sp]、[prev_ip]都在内存中分别为prev->thread.sp、prev->thread.ip,而最后一个参数则与寄存器EAX结合,对应于参数中的last。而输入部则有4个参数,其中[next_sp]、[next_ip]在内存中,分别为next->thread.sp与next->thread.ip,剩余的两个参数则与寄存器EAX,EDX结合,分别对应prev,next。

先看开头有两条push指令和结尾处有两条pop指令,再看14行将当前的esp,也就是当前进程的prev的内核态的堆栈指针存入prev->thread.sp,第15行又将新收到调度要进入运行的进程next的内核态的堆栈之争next->thread.sp置入esp。这样一来,CPU在第15行与第16行这两条指令之间就已经切换了堆栈。假定我们有A,B两个进程,在本次切换中prev指向A,而next指向B。也就是说,在本次切换中A为要“调离”的进程,而B为要“切入”的进程。那么,在这里的第12到15行是在使用A的堆栈,而从第16行开始就是在用B的堆栈了。换言之,从第16行开始,“当前进程”,已经是B而不是A了。在内核代码中当需要访问当前进程的task_struct结构时使用的指针current时实际上是宏定义,它根据当前的堆栈指针的ESP计算出所需的地址。如果第16行处引用current的话,那就是已经指向B的task_struct结构了。所以进程切换其实在第15行指令执行完就已经完成了。但是,构成一个进程的另一个要素是程序的执行,所以还要进行其他步骤。由于12,13行事push进A的堆栈,而在21行至22行从B的堆栈中POP出来,本质就是恢复新切入的进程在上一次被调离时的push进堆栈的内容。理论上讲,进程的切换过程中,多个进程都已经在执行了只是暂时的撤离cpu,所以切换过程就是在堆栈之间进程切换。

那么如何完成程序执行的切换,看一下之后的16行至20行。第16行的[prev_ip]所在位置,实际上就是将第21行的pop指令所在的地址保持在prev->thread.ip中,作为进程A下一次被调度运行而切入时的“返回”地址。然后,又将next->thread.ip压入堆栈。所以,这里的next->thread.ip正是进程B上一次被调离时在第16行中保存的。它也指向这里的[prev_ip],即21行的pop指令。接着,在19行通过jmp命令,而不是call命令,转入了一个函数__switch_to()。暂时不讨论__switch_to(),当CPU执行到哪里的iret指令时,由于是通过jmp指令转过去的,最后进入对战的next->thread.ip就变成了返回地址,而这就是[prev_ip]所在的地址,也就是21行的pop指令所在的地址。由于每个进程在被调离时都要执行这里的第16行,这就决定了每个进程在收到调度恢复运行时都是从这里的第21行开始。上面都是已有进程的切换。但新创建的进程会是怎么样切换的。新创建的进程并没有在“上一次调离时”执行过这里的第12至16行,所以要将其task_struct结构中的thread.ip事先设置好,并且设置“返回地址”时不一定是[prev_ip]所在的地址,这里取决于内核态堆栈的设置。

那么,这个地址在copy_thread()中确定,由于未能完整的阅读整个process_32.c中的源码,以下内容只是推测如有错误请博友指正,在新进程被创建时,在父进程执行完fork之后只会返回会从调用系统调用时状态,而子进程的“返回地址”也被设置成这个地址,所以__switch_to()一执行ret指令就直接回到了那里。

最后,在__switch_to()中到底干了些什么呢?看一下Linux3.9.7中/arch/x86/kernel/process_32.c。这里主要处理的是TSS,核心在16行,把next_p->thread.esp0装入对应于本地cpu的tss的esp0字段;任何由sysenter汇编指令产生的从用户态到内核态的特权级转换将把这个地址拷贝到esp寄存器中。其次段寄存器gs中的内容也做了相应的切换。然后把next进程使用的县城局部存储(TLS)段装入本地CPU的全局描述符表;三个段选择符保存在进程描述符内的tls_array数组中。所以,除了刚创建新进程外,所有进程在受到调度时的切入点都在宏定义switch_to()中的标号[prev_ip],一直运行到在下一次进入switch_to()以后在__switch_to()中执行ret为止。或者也可以认为,切入点中全面的分析了函数在堆栈调用过程中堆栈中变量及寄存器的数值的变化。

理解函数调用栈最重要的两点是:栈的结构,ebp,esp,eip寄存器的作用。从这个基础上发现堆栈在操作系统的工作中的作用:通过堆栈来保存任务切换过程中的上下文,进而在顺序执行的基础上支持了多任务操作。

函数调用堆栈过程可分解为:参数入栈时的push操作,一般会有多个。另一方面在调用过程中肯定会使用call指令,call指令内部其实还暗含了一个将返回地址(即call指令下一条指令的地址)压栈的动作。Linux中gcc都会在每个函数体之前插入类似如下指令:即,在程序执行到一个函数的真正函数体时,已经有以下数据顺序入栈:参数,返回地址,EBP。由此得到类似如下的栈结构(参数入栈顺序跟调用方式有关)如图2。

“pushl %ebp”“movl%esp,%ebp”先将EBP入栈,然后将栈顶指针ESP赋值给EBP。此时EBP寄存器中存储着栈中的一个地址(原EBP入栈后的栈顶),从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,而该地址处又存储着上一层函数调用时的EBP值。

一般而言,ss:[ebp+4]处为返回地址,ss:[ebp+8]处为第一个参数值(最后一个入栈的参数值,此处假设其占用4字节内存),ss:[ebp-4]处为第一个局部变量,ss:[ebp]处为上一层EBP值。

由于ebp中的地址处总是“上一层函数调用时的ebp值”,而在每一层函数调用中,都能通过当时的EBP值“向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值”。如此形成递归,直至到达栈底。这就是函数调用栈。

中断机制:中断是CPU提供的一种功能,不属于linux内核,而对应的中断处理程序则属于内核控制,在执行新指令前,控制单元会检查在执行前一条指令的过程中是否有中断发生,如果有控制大院就会抛下指令,进入下面流程:①确定与中断关联的向量i(0

所以整个中断的处理过程中,对于CPU,处理过程是一样的,中断现行程序,转到中断服务程序处执行,回到被中断的程序继续执行。CPU总共可以处理256种中断。

那什么是中断处理程序,在介绍中断处理程序之前,让我们先了解一下什么是软中断、tasklet和工作队列:

软中断:软中断是利用硬件中断的概念,用软件方式进行模拟,实现宏观上的异步执行效果。很多情况下,软中断和“信号”有些类似,同时,软中断又是和硬中断相对应的,“硬中断是外部设备对CPU的中断”,“软中断通常是硬中断服务程序对内核的中断”,“信号则是由内核(或其他进程)对某个进程的中断”(《Linux内核源代码情景分析》第三章)。

sklet:tasklet是由软中断引出的,内核定义了两个软中断掩码HI_SOFTIRQ和TASKLET_SOFTIRQ(两者优先级不同),这两个掩码对应的软中断处理函数作为入口,进入tasklet处理过程。

队列:定义一个work结构(包含了处理函数),然后在中断处理过程中调用schedule_work函数,work便被添加到workqueue中,等待处理。工作队列有着自己的处理线程,这些work被推迟到这些线程中去处理。内核默认启动了一个工作队列,对应一组工作线程events/n(n代表处理器编号,这样的线程有n个)。驱动程序可以直接向这个工作队列添加任务。某些驱动程序还可能会创建并使用属于自己的工作队列。

那么,让我们看看目前最主要引起中断的原因:IO中断,时钟中断,系统调用。所谓的中断处理程序就是在响应一个特定中断的时候,内核会执行一个函数,该函数就是中断处理程序。图3为中断处理程序的处理流程。

从图3可以看出,每个中断处理都要经历保存、处理与恢复过程,我们可以总结出以下步骤:①保存现场。②执行具体的中断处理程序。③从中断处理返回。④恢复现场。

那么为什么会有软中断,tasklet,和工作队列呢?由于中断处理程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,Linux内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。因此,内核把中断处理分为两部分:上半部(tophalf)和下半部(bottomhalf),上半部(就是中断处理程序)内核立即执行,而下半部(就是一些内核函数)留着稍后处理。对应于上下半部的处理,才有了以上这些概念。

那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。

3 小结

Linux以它的高效性和灵活性著称,Linux模块化的设计结构,使得它既能在价格昂贵的工作站上运行,也能够在廉价的pc机上实现全部的Unix特性,具有多任务、多用户的能力。Linux是在GNU公共许可权限下免费获得的,是一个符合POSIX标准的操作系统。Linux操作系统软件包不仅包括完整的Linux操作系统,而且还包括了文本编辑器、高级语言编译器等应用软件。它还包括带有多个窗口管理器的X-Windows图形用户界面,如同我们使用Windows NT一样,允许我们使用窗口、图标和菜单对系统进行操作。

参考文献:

[1]董荣胜.《九校联盟(C9)计算机基础教学发展战略联合声明》呼唤教育的转型[J].中国大学教学,2010(10).

[2]九校联盟(C9)计算机基础教学发展战略联合声明[J].中国大学教学,2010(09).

[3]何钦铭,陆汉权,冯博琴.计算机基础教学的核心任务是计算思维能力的培养——《九校联盟(C9)计算机基础教学发展战略联合声明》解读[J].中国大学教学,2010(09).

上一篇:图像识别技术在安全防范系统中的应用 下一篇:无线传感器网络在航标遥测遥控系统中的应用研...