内核复习

  • 格式:docx
  • 大小:667.35 KB
  • 文档页数:20

一、1、操作系统:指在整个系统中负责完成最基本功能和系统管理的那些部分。

这些部分应该包括内核、设备驱动程序、启动引导程序、命令行shell或者其他种类的用户界面、基本的文件管理工具和系统工具。

2、内核:①内核是操作系统的内在核心。

系统其他部分必须依靠内核这部分软件提供的服务。

像管理硬件设备,分配系统资源等等。

内核有时候被称作是超级管理者或者是操作系统核心。

②通常一个内核由负责响应中断的中断服务程序,负责管理多个进程从而分享处理器时间的调度程序,负责管理进程地址空间的内存管理程序和网络、进程间通信等系统服务程序共同组成。

3内核空间:对于提供保护机制的现代系统来说,内核独立于普通应用程序,它一般处于系统态,拥有受保护的内存空间和访问硬件设备的所有权限。

这种系统态和被保护起来的内存空间,统称为内核空间。

用户空间:相对的,应用程序在用户空间执行。

它们只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,不能直接访问硬件,还有其他一些使用限制。

当内核运行的时候,系统以内核态进入内核空间,相反,普通用户程序以用户态进入用户空间。

4、进程上下文:当一个应用程序请求执行一条系统调用,我们说内核正在代其执行。

进一步解释,应用程序被称为通过系统调用在内核空间运行,而内核被称为运行于进程上下文中。

中断上下文:①硬件设备想和系统通信的时候,它首先要发出一个异步的中断信号去打断内核正在执行的工作。

②中断通常对应着一个中断号,内核通过这个中断号查找相应的中断服务程序,并调用这个程序响应和处理中断。

③许多操作系统的中断服务程序都不在进程上下文中执行。

它们在一个与所有进程都无关的、专门的中断上下文中运行。

5、上下文代表着内核活动的范围。

实际上我们可以将处理器在任何指定时间点上的活动范围概括为下列三者之一:运行于内核空间,处于进程上下文,代表某个特定的进程执行。

运行干内核空间,处于中断上下文,与任何进程无关,处理某个特定的中断。

运行于用户空间,执行用户进程。

6、单内核与微内核设计之比较:单内核(Linux、Unix):所谓单内核就是把它从整体上作为一个单独的大过程来实现,并同时运行在一个单独的地址空间。

因此,这样的内核通常以单个静态二进制文件的形式存放于磁盘。

所有内核服务都在这样的一个大内核空间中运行。

内核各个模块可以直接调用函数来进行通信。

微内核(Window NT):微内核的功能被划分为独立的过程,每个过程叫做一个服务器。

所有的服务器都保持独立并运行在各自的地址空间。

服务器的各自独立有效地避免了一个服务器的失效祸及另一个。

服务器间的通信需要通过消息传递来实现。

二、1、相对于用户空间内应用程序的开发,内核开发有很大的不同。

最重要的差异包括以下几种:①内核编程时不能访问C库。

②内核编程时必须使用GNU C。

③内核编程时缺乏像用户空间那样的内存保护机制。

④内核编程时浮点数很难使用。

⑤内核只有一个很小的定长堆栈。

⑥由于内核支持异步中断、抢占和SMP,因此必须时刻注意同步和并发。

⑦要考虑可移植性的重要性。

2、①与用户空间的应用程序不同,内核不能链接使用标准C函数库,对内核来说,完整的c库太大了,但是,大部分常用的C库函数在内核中都已经得到了实现。

②在内核编程中,只能使用内核源代码树中的头文件。

③内核代码虽然无法调用printf(),但它可以调用printk()函数。

3、内联(inline)函数:定义一个内联函数的时候,需要使用static作为关键字,并且用inline限定它。

比如:static inline void dog(unigned long tail_size){}定义:内联函数必须在使用之前就定义好,否则编译器就没法把这个函数展开。

实践中一般在头文件中定义内联函数。

由于使用了static作为关键字进行限制,所以编译时不会为内联函数单独建立一个函数体。

如果一个内联函数仅仅在某个源文件中使用,那么也可以把它定义在该文件开始的地方。

4、没有内存保护机制:①如果一个用户程序试图进行一次非法的内存访问,内核会发现这个错误,发送SIGSEGV,并结束整个进程。

然而,如果是内核自己非法访问了内存,那后果就很难控制了。

内核中发生的内存错误会导致oops,这是内核中出现的最常见的一类错误。

在内核中,不应该去做访问非法的内存地址,引用空指针之类的事情,否则它可能会死掉,却根本不知会你一声——在内核里,风险常常会比外面大一些。

②此外,内核中的内存都不分页。

也就是说,每用掉一个字节,物理内存就减少一个字节。

所以,在你想往内核里加入什么新功能的时候,要记住这一点。

5、不要轻易在内核中使用浮点数:①在用户空间的进程内进行浮点操作的时候,内核会完成从整数操作到浮点数操作的模式转换。

在执行浮点指令时到底会做些什么,因体系结构不同,内核的选择也不同,但是,内核通常捕获陷阱并做相应处理。

②和用户空间进程不同,内核并不能完美地支持浮点操作,因为它本身不能陷入。

在内核中使用浮点数时,除了要人工保存和恢复浮点寄存器,还有其他一些琐碎的事情要做。

如果要直截了当的回答,那就是,别这么做了,不要在内核中使用浮点数。

6、容积小而固定的栈:①用户空间的程序可以从栈上分配大量的空间来存放变量,甚至巨大的结构体或者是包含许多数据项的数组都没有问题。

之所以可以这么做,是因为用户空间的栈本身比较大,而且还能动态的增长。

②内核栈的准确大小随体系结构而变。

在x86上,栈的大小在编译时配置,可以是4KB也可以是8KB。

从历史上说,内核栈的大小是两页,这就意味着,32位机的内核栈是8KB,而64位机是16KB,这是固定不变的。

每个处理器都有自己的栈。

7、同步和并发:①内核很容易产生竞争条件。

②Linux是抢占多任务操作系统。

③Linux内核支持多处理器系统。

④中断是异步到来的,完全不顾及当前正在执行的代码。

⑤Linux内核可以抢占。

常用的解决竞争的办法是自旋锁和信号量。

8、可移植性的重要性:Linux是一个可移植的操作系统,并且一直保持这种特点。

也就是说,大部分C代码应该与体系结构无关,在许多不同体系结构的计算机上都能够编译和执行,因此,必须把体系结构相关的代码从内核代码树的特定目录中适当地分离出来。

三、1、嵌入式linux系统的组成包括三部分:bootloader程序,linux内核,根文件系统。

①Bootloader程序:系统上电时第一个要执行的程序,主要功能是为启动linux内核准备环境;②Linux内核:操作系统的核心部分,是一个单一的文件(uImage);③根文件系统:根文件系统包括操作系统的一些系统工具,C函数库等的东西。

2、嵌入式linux软件开发的特点:①需要交叉编译:1、硬件平台处理器较慢,内存和外存容量小等等;2、把软件开发放在高性能的PC机上进行;3、PC机上CPU指令集与嵌入式CPU的指令集是不同的,因此,在PC机上开发嵌入式软件需要交叉编译。

②需要移植:1、由于嵌入式系统是一个软硬件定制的系统,硬件平台各不相同,软件设计需要根据不同的硬件设备来添加或修改相应的代码,这就是移植。

3、交叉开发环境是指编译、链接和调试嵌入式应用软件的环境,它与运行嵌入式应用软件的环境有所不同,通常采用宿主机-目标机模式。

4、5、嵌入式软件开发过程中的交叉调试与本地软件开发过程中的调试方式有所差别:本地软件开发:①调试器与被调试的程序往往运行在同一台计算机上。

②调试器是一个单独运行着的进程,它通过操作系统提供的调试接口来控制被调试的进程。

嵌入式软件开发:①调试时采用的是在宿主机和目标机之间进行的交叉调试。

②调试器运行在宿主机,但被调试的进程却是运行在目标板。

③调试器和被调试进程通过串口或者网络进行通信,调试器可以控制、访问被调试进程,读取被调试进程的当前状态,并能够改变被调试进程的运行状态。

六、1、一个API定义了一组应用程序使用的编程接口,应用编程接口(API)与系统调用的关系:①应用程序编程接口实际上并不需要和内核提供的系统调用对应。

②一个API可以实现成一个系统调用;③一个API也可以通过调用多个系统调用来实现④一个API也可以完全不使用任何系统调用。

实际上,API可以在各种不同的操作系统上实现,给应用程序提供完全相同的接口。

2、系统调用(在Linux中常称作syscalls):通常通过函数进行调用。

①它们通常都需要定义一个或几个参数。

②系统调用还会通过一个long类型的返回值来表示成功或者错误。

③通常,但也不绝对,用一个负的返回值来表明错误。

④返回一个0值通常(当然仍不是绝对的)表明成功。

Unix系统调用在出现错误的时候会把错误码写入errno全局变量。

通过调用perror()库函数,可以把该变量翻译成用户可以理解的错误字符串。

3、系统调用号:①每个系统调用被赋予一个系统调用号。

②系统调用号关联系统调用。

当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行哪个系统调用;进程不会提及系统调用的名称。

③系统调用号相当关键,一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。

④此外,如果一个系统调用被删除,它所占用的系统调用号也不允许被回收利用,否则,以前编译过的代码会调用这个系统调用。

4、系统调用表(sys_call_table):①表中存放每个的系统调用的入口地址(函数指针);②每一个体系结构都有对应的一个系统调用表;5、系统调用的处理过程:①用户空间的程序无法直接执行内核代码。

②所以,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序来执行该系统调用了。

③通知内核的机制是靠软中断实现的:通过引发一个异常来促使系统切换到内核态去执行异常处理程序。

6、系统调用的实现:1)参数验证:①系统调用必须仔细检查它们所有的参数是否合法有效。

②系统调用在内核空间执行,如果任由用户将不合法的输入传递给内核,那么系统的安全和稳定将面临极大的考验。

③举例来说,与文件I/O相关的系统调用必须检查文件描述符是否有效。

与进程相关的函数必须检查提供的PID是否有效。

必须检查每个参数,保证它们不但合法有效,而且正确。

2)检查用户提供的指针是否有效。

在接收一个用户空间的指针之前,内核必须保证:①指针指向的内存区域属于用户空间。

进程决不能哄骗内核去读内核空间的数据。

②指针指向的内存区域在进程的地址空间里。

进程决不能哄骗内核去读其他进程的数据。

③如果是读,该内存应被标记为可读。

如果是写,该内存应被标记为可写。

进程决不能绕过内存访问限制。

3)最后一项检查针对是否有合法权限:①新的系统允许检查针对特定资源的特殊权限。

调用者可以使用capable()函数来检查是否有权能对指定的资源进行操作,如果它返回非零值,调用者就有权进行操作,返回零则无权操作。