《Linux内核设计与实现》笔记参考:《Linux内核设计与实现》读书笔记第1章介绍运行在内核地址空间的LinuxKernel。1.简单。2.效率:所有内核都在一个很大的地址空间中,所以内核函数之间的调用类似于调用函数,几乎没有性能开销。一个函数的崩溃会导致整个内核无法使用。微内核内核在功能上分为独立的进程。每个进程都在自己的地址空间中独立运行。1、安全性:内核各项服务独立运行,一个服务出现故障不会影响其他服务。内核服务之间的进程间通信复杂且低效。因为IPC机制的开销要大于函数调用,而且因为涉及到内核空间和用户空间的上下文切换,所以消息传递需要一定的周期,而单内核中简单的函数调用则没有这些开销。尽管Linux内核基于单个内核,但它运行在单独的内核地址空间中。但是经过这么多年的发展,它也具备了微内核的一些特点。(体现Linux实用性至上的原则)主要有以下特点:模块化设计,支持动态加载内核模块,支持对称多处理(SMP)内核可抢占(preemptive),让内核运行的任务有能力先执行,支持内核线程,不区分线程和进程2.内核版本号内核的版本号主要由四个数组组成。例如版本号:2.6.26.1其中:2-主版本号6-次版本号或次版本号26-修订号1-稳定版本号次版本号表示这个版本是稳定版本(偶数)还是开发版(奇数),上例中的版本号为稳定版。稳定版本可用于企业级环境。修订号升级包括错误修复、新驱动程序和添加新功能。稳定版本号主要是对一些关键bug的修改。第二章从内核说起1.获取内核源码内核是开源的,获取源码非常方便。参考以下网址通过git或直接下载压缩源码包。http://www.kernel.org2。内核源代码目录结构arch特定架构codeblock块设备I/O层cryo加密APIDocumentation内核源代码文档driversdevicedriverfirmwaredevicefirmwarerequiredtousecertaindriversfsVFSandvariousfilesystemsincludekernelheaderfilesinitkernelboot和初始化ipc进程间通信代码内核核心子系统,如调度程序lib相同的内核函数mm内存管理子系统和VMnet网络子系统示例示例,示例代码脚本编译内核使用的脚本securityLinux安全模块声音语音子系统usr早期用户空间代码(所谓的initramfs)toolsLinux开发中有用的工具virt虚拟化基础设施内核已经用yum更新了。这部分将在手动编译后添加。安装新内核后,重启时会提示进入哪个内核。多次安装新内核时,引导列表会很长(因为内核版本很多),不是很方便。这里有3种删除那些不用的内核的方法:(如何安装选择相应的删除方法)rpm删除方法rpm-qa|grepkernel*(查找所有linux内核版本)rpm-ekernel-(要删除的版本)yumdelete方法yumremovekernel-(要删除的版本)手动删除删除/lib/modules/目录下不需要的内核库文件delete/usr/src/kernel/目录下不需要的内核源代码删除/boot目录下启动的核心文件和内核镜像更改grub的配置,删除不需要的内核启动列表连最常用的printf函数都没有,但幸运的是有一个printk函数代替。4.2使用GNUC因为使用了GNUC,所以GNUC的一些扩展经常被用在所有内核中:内联函数内联函数在编译时会在调用的地方进行扩展,减少了函数调用的开销,性能更好.但是,频繁使用内联函数也会使代码变长,从而在运行时占用更多的内存。因此,使用内联函数最好满足以下几点:函数小,会被重复调用,对程序的时间要求比较严格。内联函数的例子:staticinlinevoidsample();内联汇编内联汇编用于靠近底层或对执行时间要求严格的地方。例子如下:unsignedintlow,high;asmvolatile("rdtsc":"=a"(low),"=d"(high));/*low和high分别包含64位时间戳的低32位和32位的高位*/branchstatement如果你能提前判断一个if语句是常为真还是常为假,那么你就可以使用unlikely和likely来优化这个判断的代码。/*如果错误大部分时间为0(假)*/if(不太可能(错误)){/*...*/*如果成功大部分时间不为0(真)*/if(可能(success)){/*...*/}4.3没有内存保护因为内核是最底层的程序,如果内核访问非法内存,整个系统就会挂掉!因此,内核开发的风险要大于用户程序开发的风险。内核中的内存没有分页。每使用一个字节的内存,物理内存就会减少一个字节。所以在内核中使用内存一定要慎重。4.4不使用浮点数,内核无法完美支持浮点运算。在使用浮点数时,需要手动保存和恢复浮点寄存器等繁琐的操作。4.5内核栈的大小小且固定。内核堆栈的大小是在编译内核时确定的。对于不同的体系结构,内核堆栈的大小是固定的,尽管有所不同。如何查看内核堆栈的大小:ulimit-a|grep"stacksize"4.6同步与并发Linux是一个多用户操作系统,因此必须处理好同步与并发操作,以防止因竞争而导致死锁。内核容易出现竞争条件。与单线程用户空间程序不同,内核的许多特性需要并发访问共享数据,这就需要同步机制来确保不会出现竞争条件,尤其是:Linux是一个抢占式多任务操作系统。内核的进程调度器动态地调度和重新调度进程。内核必须与这些任务同步。Linux内核支持对称多处理器系统(SMP)。因此,如果没有适当的保护,同时在两个或多个处理器上执行的内核代码很可能同时访问同一个共享资源。中断异步到达,不考虑当前正在执行的代码。换句话说,如果保护不当,完全有可能在代码访问资源的时候中断到来,使得中台处理程序访问同一个资源。Linux内核是抢占式的。因此,如果保护不当,内核中的一段正在执行的代码可能会被另一段代码抢占,从而可能导致多段代码同时访问同一资源。常用的争用解决方案是自旋锁和信号量。4.7可移植性Linux内核可用于不同的实现结构,支持多种硬件。因此,在开发时要始终注意可移植性,尽量使用架构无关的代码。第三章进程管理1.进程程序本身不是进程,进程是对正在执行的程序及其相关资源的总称。可能有两个或多个不同的进程在执行同一个程序。两个或多个并发进程还可以共享许多资源,例如打开的文件和地址空间。进程和线程是程序运行时的状态,是动态变化的。进程和线程的管理操作(如创建、销毁等)均由内核实现。Linux并没有严格区分进程和线程。对于Linux,线程只是一个特殊的进程。在现代操作系统中,进程提供了两种虚拟机制:虚拟处理器和虚拟内存。虚拟处理器给进程一种错觉,让这些进程感觉它们在独占使用处理器。虚拟内存让一个进程在分配和管理内存时,感觉自己拥有整个系统的所有内存资源。每个进程都有自己的虚拟处理器和虚拟内存。进程中的线程可以共享虚拟内存,但每个线程都有自己的虚拟处理器。进程创建和退出:进程在创建的那一刻就活跃起来。在Linux系统上,这通常是调用fork()系统的结果,它通过复制现有进程来创建一个全新的进程。调用fork()的进程称为父进程,新创建的进程称为子进程。在调用结束时,在与返回点相同的位置,父进程恢复执行,子进程开始执行。fork()系统调用从内核返回两次:一次返回到父进程,一次返回到新生成的子进程。创建一个新的进程就是立即执行一个新的不同的程序,然后调用exec()这??组函数就可以创建一个新的地址空间并将新的程序载入其中。在现代Linux内核中,fork()实际上是由clone()系统调用实现的。最终,程序通过exit()系统调用退出执行。该函数终止进程并释放其占用的资源。进程退出执行后,它被设置为僵尸状态,直到其父进程调用wait()或waitpid()。内核中进程的信息主要存放在task_struct(include/linux/sched.h)中。对于同一进程或线程,进程IDPID和线程IDTID是相等的。在Linux中,可以使用ps命令查看所有进程的信息:ps-eopid,tid,ppid,comm2。进程描述符和任务结构内核将进程列表存储在一个称为任务队列(tasklist)的双向循环链表中。链表中的每一项都是一个task_struct类型的结构,称为进程描述符(processdescriptor),定义在include/linux/sched.h文件中。进程描述符包含有关特定进程的所有信息。进程描述符中包含的数据可以完整地描述一个正在执行的程序:它打开的文件、进程的地址空间、挂起的信号、进程的状态等等。
