3.8
一个函数应该功能单一并且实现精准;将一个函数分解为一些更短小的函数的组合不会带来危害;
如果你担心函数调用导致的开销,可以使用inline关键字;
使用typedef要谨慎,只有在确实需要的时候再用它,如果仅仅是为了少敲打几个键盘,别使用它;
结构初始化的时候必须在它的成员前加上结构标识符。这种初始化能避免错误地使用其他结构而引发一个初始化错误。它也支持使用忽略值。不幸的是,C99标准改用了一种丑陋的格式来表示这种标识符,于是gcc就再也不支持原来GNU风格的标识符了,尽管它看起来确实更帅一些。结果,内核代码现在必须都要使用新的C99标识符格式了,不管它有多难看:
struct foo my_foo = {
.a = INITIAL_A,
.b = INITIAL_B,
};
其中a和b是结构体foo的成员,而INITIAL_A和INITIAL_B是它们对应的初始值。如果一个字段没有给初始值,那么它就会被设置为ANSI C规定的默认值(如指针被设为NULL,整形被设为0,浮点数被设置为0.0)。
3.6
设备模型的核心部分就是kobject;sysfs的真正面目:一个用户空间的文件系统,用来表示内核中kobject对象的层次结构;
ksets可把kobject集中到一个集合中,而ktype描述相关类型kobject所共有的特性,它们之间的重要区别在于:具有相同ktype的kobject可以分组到不同的ksets中。
subsystem是一个或多个ksets的大集合。ksets包含kobject,而subsystems包含ksets;
sysfs文件系统是一个处于内存中的虚拟文件系统,它为我们提供了kobject对象层次结构的视图。帮助用户能以一个简单文件系统的方式来观察系统中各种设备的拓扑结构;
在标准的Linux系统上,用户空间的守护进程klogd从记录缓冲区获取内核消息,再通过syslogd守护进程将它们保存在系统日志文件中。klogd程序既可以从/proc/kmsg文件中也可以通过syslog()系统调用读取这些消息。默认情况下,它选择读取/proc方式实现。syslogd守护进程把它接收到的所有消息加进一个文件中,该文件默认是/var/log/messages。
开发版的2.5内核引入了kallsyms特性,它可以通过定义CONFIG_KALLSYMS配置选项启用。该选项可以载入内核镜像对应的内存地址的符号的名称,所以内核可以打印解码好的跟踪线索。另外,这样做会使内核变大一些,因为地址对应的符号名称必须始终驻留在内核所在的内存上。
神奇系统请求键是调试和挽救垂危系统所必须的一种工具。SysRq -s将“脏”缓冲区跟硬盘交换分区同步,SysRq -u卸载所有的文件系统,SysRq -b重启设备。在一行内发送这个三个键的组合可以重新启动濒临死亡的系统,这笔直接按下机器的Reset键要安全一些;
人们说某个机器是多少“位“的时候,他们其实说的就是该机的字长。比如说,当人们说奔腾是32位芯片时,他们的意思是奔腾的字长为32位,也就是4字节;
一般来说,地址空间的大小等于字长,至少Linux支持的体系结构中都是这样的。此外,C语言定义的long类型总对等于机器的字长,而int类型有时会比字长小。比如说,Alpha是64位机。所以,它的寄存器、指针和long类型都是64位长度的,而int类型是32位的。Alpha机每一次可以访问和操作一个64位长的数据;在Linux中,一个字就是代表处理器的字长;C语言中long类型的长度就被确定为机器的字长;事实上,对于Linux支持的64位体系结构来说,long和int长度是不同的,int是32位的,而long是64位的。但对于我们所熟悉的32位体系结构而言,两种数据类型都是32位的;
因为用户空间使用的数据类型和内核空间的数据类型不一定要相互关联。sparc64位体系结构就提供了32位的用户空间,其中指针、int和long的长度都是32位。而在内核空间,它的int长度是32位,指针和long的长度却是64位;
牢记下述准则:
*ANSIC C标准规定,一个char的长度一定是8位;
*尽管没有规定int类型的长度是32位,但在Linux当前所有支持的体系结构中,它都是32位的;
*short类型也类似,在当前所有支持的体系结构中,虽然没有明文规定,但是它都是16位的;
另外一个不透明数据类型的例子是atomic_t,它放置的是一个可以进行原子操作的整形值。尽管这种类型就是一个int,但利用不透明类型可以帮助确保这些数据只在特殊的有关原子操作的函数中才会被使用;
对齐是跟数据块在内存中的位置相关的话题。如果一个变量的内存地址正好是它长度的整数倍,它就被称作是自然对齐的。举例来说,对于一个32位类型的数据,如果它在内存中的地址刚好可以被4整除(也就是最低两位为0),那它就是自然对齐的。也就是说,一个大小为2n字节的数据类型n,它的地址的最低有效位的后n位都应该为0。
为了保证结构体中每一个成员都能够自然对齐,结构体要被填补。这点确保了当处理器访问结构中一个给定元素时,元素本身对对齐的;
注意,ANSI C明确规定不允许编译器改变结构体内成员对象的次序——它总是由你,程序员来决定;
注意,使用高位优先的体系结构把最高字节位存放在最小的内存地址上;
绝对不要假定时钟中断发生的频率,也就是每秒产生的jiffies数目。相反,应该使用Hz来正确计量时间。如下:
HZ /* 1秒 */
( 2*HZ ) /* 2秒 */
( HZ/2 ) /* 半秒 */
( HZ/100 ) /* 10毫秒 */
( 2*HZ/100 ) /* 20毫秒 */
3.5
通常情况下,每个进程都有惟一的这种平坦(连续)地址空间,而且进程地址空间之间彼此互不相干。两个不同的进程可以在它们各自地址空间的相同地址内存放不同的数据。但是进程之间也可以选择共享地址空间,我们称这样的进程为线程;
内存地址是一个给定的值,这个值表示的是进程32位地址空间中的一个特定的字节;在地址空间中,我们更为关心的是进程有权访问的虚拟内存地址空间,比如0x80480000-0x894c000;这些可被访问的合法地址空间被称为内存区域,通过内核,进程可以给自己的地址空间动态地添加或减少内存区域;内存区域可以包含各种内存对象,比如:
*可执行文件代码的内存映射,称为代码段(text section);
*可执行文件的已初始化全局变量的内存映射,称为数据段(data section);
*包含未初始化全局变量,也就是bss段的零页(页面中的信息全部为0,所以可用于映射bss段等目的)的内存映射;
注:bss,block started by symbol;因为未初始化的变量没有对应的值,所以不需要存放在可执行对象中。但是因为C标准强制规定未初始化的全局变量要被赋予特殊的默认值(基本上是0值),所以内核要将变量(未赋值的)从可执行代码载入到内存中,然后将零页映射到该片内存上,于是这些未初始化变量就被赋予了0值。这样做避免了在目标文件中显示地进行初始化,减少空间浪费;
*用于进程用户空间栈的零页的内存映射;
*每一个诸如C库或动态连接程序等共享库的代码段、数据段和bss也会被载入进程的地址空间;
*任何内存映射文件;
*任何共享内存段;
*任何匿名的内存映射,比如由malloc()分配的内存;
可以看到,在执行的进程中,每个不同的内存片段都对应一个独立的内存区域:栈、对象代码、全局变量、被映射的文件等等;
如果父进程希望和其子进程共享地址空间,可以在调用clone()时,设置CLONE_VM标志。我们把这样的进程称作线程;是否共享地址空间,几乎是进程和Linux所谓的线程本质上的唯一区别;线程对内核来说仅仅是一个共享特定资源的进程而已;
内存区域在内核中也经常被称作虚拟内存区域或VMA;
注意,代码段具有我们所要求的可读且可执行权限;数据段和bss(它们都包含全局数据变量)具有可读、可写但不可执行权限;而堆栈则可读、可写,甚至还可执行——虽然这点并不常用到;
虽然应用程序操作的对象是映射到物理内存之上的虚拟内存,但是处理器直接操作的却是物理内存。所以当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。
Linux中使用三级页表完成地址转换。由于几乎每次对虚拟内存中的页面访问都必须先解析它,从而得到物理内存中的对应地址,所以页表操作的性能非常关键。但不幸的是,搜索内存中的物理地址速度是很有限,因此,为了加快搜索,多数体系结构都实现了一个翻译后缓冲器(translation lookaside buffer,TLB)。TLB作为一个将虚拟地址映射到物理地址的硬件缓存,当请求访问一个虚拟地址时,处理器将首先检查TLB中是否缓存了该虚拟地址到物理地址的映射,如果在缓存中直接命中,物理地址立刻返回,否则,如果未命中,那么就需要再通过页表搜素需要的物理地址;
页高速缓存(cache)是Linux内核实现的一种主要磁盘缓存。它主要用来减少对磁盘的I/O操作。具体的讲,是通过把磁盘中的数据缓存到物理内寸中,把对磁盘的访问变为对物理内存的访问;页高速缓存是由RAM中的物理页组成的,缓存中每一页都对应着磁盘中的多个块。每当内核开始执行一个页I/O操作时(通常是对普通文件中页大小的块进行磁盘操作),首先会检查需要的数据是否在高速缓存中,如果在,那么内核就直接使用高速缓存中的数据,从而避免访问磁盘;
并非是read()和write()系统调用执行实际的页I/O操作,而是通过特定文件系统提供的操作:file->file_op->read()和file->f_op->write();
一个物理页可能由多个不连续的物理磁盘块组成。比如,x86体系结构中一个物理页的大小是4KB,而大多数文件系统的磁盘块大小仅仅512字节,所以8个块才可以填满一个页面。另外,因为文件本身可能分布在磁盘各个位置,所以页面中映射的块也不需要连续;
现在linux只有唯一的磁盘缓存——页高速缓存;虽然如此,内核人仍然需要在内存中使用缓冲来表示磁盘块,幸好,缓冲是用页映射块的,所以它正好在页高速缓存中;
由于页高速缓存的缓存作用,写操作实际上会被延迟。当页高速缓存中的数据比后台存储的数据更新时,那么该数据就被称作脏数据。在内存中累积起来的脏页最终必须被写回磁盘;
不要把要处理的内核源代码树放在/usr/src/linux下,而要移到你的home目录下某个方便访问的地方;
编译后的模块将被装入到目录/lib/modules/version/kernel/下。下面的构建命令用来安装编译的模块到合适的目录下:
make modules_install
若想产生内核依赖关系的信息,root用户可运行命令:depmod
为了执行更快的更新操作,可以只为新模块生成依赖信息,而不是生成所有的依赖关系,这时root用户可运行命令:depmod -A
模块依赖关系信息存放在/lib/modules/version/modules.dep文件中;
insmod、rmmod这两个命令虽然简单,但是它们一点也不只能。好在系统为我们提供了一个更先进的工具modprobe,它提供了模块依赖性分析,错误只能检查,错误报告以及许多其它功能和选项。强烈建议大家使用这个命令。
modprobe module [ module parameters ]
其中,参数module指定了需要载入的模块名称;
modeprobe命令不但会加载指定的模块,而且会自动加载任何它所依赖的有关模块。所以说,它是加载模块的最佳技术。
modprobe命令也可用来从内核中卸载模块;modprobe -r modules
参数modules指定一个或多个需要卸载的模块。与rmmod命令不同,modprobe也会卸载给定模块所依赖的相关模块,其前提是这些相关模块没有被使用。
模块被载入后,就会动态连接到了内核。注意,它与用户空间中的动态连接库类似,只有当被显式导出后的外部函数,才可以被动态库调用。在内核中,导出内核函数需要使用特殊的指令:EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL()。导出的内核函数可以被模块调用,而未导出的函数模块则无法被调用。导出的内核符号表被看作是导出的内核接口,甚至称为内核API。导出符号相当简单,在声明函数后,紧跟上EXPORT_SYMBOL()指令就搞定了。如果你的代码被配置为模块,那么就必须确保它被编译为模块时所用的全部接口都已被导出,否则就会产生连接错误(而且模块不能成功编译)。
3.4
磁盘寻址是整个计算机中最慢的操作之一,每一次寻址——定位硬盘磁头到特定块上的某个位置——需要话费不少时间。所以尽量缩短寻址时间无疑是提高系统性能的关键;
在内核中负责提交I/O请求的子系统被称为I/O调度程序;I/O调度程序将磁盘I/O资源分配给系统中所有挂起的块I/O请求;
进程调度程序的作用是将处理器资源分配给系统中的运行进程;进程调度程序和I/O调度程序都是将一个资源虚拟给多个对象,对进程调度程序来说,处理器被虚拟并被系统中的运行进程共享;I/O调度程序虚拟块设备给多个磁盘请求,以便降低磁盘寻址时间,确保磁盘性能的最优化;
bio结构体代表了正在现场的(活动的)以片段(segment)链表形式组织的块I/O操作;一个片段是一小块连续的内存缓冲区,这样的话,就不需要保证单个缓冲区一定要连续;所以,通过用片段来描述缓冲区,即使一个缓冲区分散在内存的多个位置上,bio结构体也能对内核保证I/O操作的执行;
总之,每一个块I/O请求都通过一个bio结构体表示;每个请求包含一个或多个块,这些块存储在bio_vec结构体数组中。这些结构体描述了每个片段在物理页中的实际位置,并且像向量一样被组织在一起;
缓冲区头的目的在于描述磁盘块和物理内存缓冲区(在特定页面上的字节序列)之间的映射关系;该结构体说明从缓冲区到块的映射关系;
当一个块被调入内存时(即,在读入后或等待写出时),它要存储在一个缓冲区中。每个缓冲区与一个块对应,它相当于是磁盘块在内存中的表示;
块包含一个或多个扇区,但大小不能超过一个页面,所以一个页可以容纳一个或多个内存中的块;
扇区对内核的重要性在于所有设备的I/O操作都必须基于扇区来进行;反过来,块是内核使用的较高层概念,它是比扇区高一层的抽象;
扇区——设备的最小寻址单元,有时会被称作“硬扇区”或“设备块”;
块——文件系统的最小寻址单元,有时会被称作“文件块”或“I/O块”;
块设备中最小的可寻址单元是扇区。扇区大小一般是2的整数倍,而最常见的大小是512个字节;
块是文件系统的一种抽象——只能基于块来访问文件系统。
虽然物理磁盘寻址是按照扇区级进行的,但是内核执行的所有磁盘操作都是按照块进行的;由于扇区是设备的最小可寻址单元,所以块不能比扇区还小,只能数倍于扇区大小;另外内核(对有扇区的硬件设备)还要求块大小是2的整数倍,而且不能超过一个页的长度;所以,对块大小的最终要求是,必须是扇区大小的2的整数倍,并且要小于页面大小;所以通常块大小是512字节、1K或4K;
系统中能够随机(不需要按顺序)访问固定大小数据片(chunk)的设备被称作块设备,这些数据片就称作块;最常见的块设备是硬盘;
另一种基本的设备类型是字符设备。字符设备按照字符流的方式被有序访问,像串口和键盘就都属于字符设备;
如果一个硬件设备是以字符流的方式被访问的话,那就应该将它归于字符设备;
如果一个设备是随机(无序的)访问的,那么它就属于块设备;
linux支持多种操作系统,从本地操作系统,如ext2和ext3,到网络文件系统,如NFS;
VFS层提供给这些不同文件系统一个统一的实现框架,而且还提供了能和标准系统调用交互工作的统一接口;
由于VFS层的存在,使得在Linux上实现新文件系统能够的工作变得简单起来,它可以轻松地使这些文件系统通过标准Unix系统调换用而协同工作;
VFS把目录当作文件对待,所以在路径/bin/vi中,bin和vi都属于文件——bin是特殊的目录文件,而vi是一个普通文件,路径中的每个组成部分都由一个索引节点对象表示;/、bin和vi都属于目录项对象。前两个是目录,最后一个是普通文件;
必须明确一点:在路径中,包括普通文件在内,每一个部分都是目录项对象;
一个索引节点代表文件系统中的一个文件;
基于内存的文件系统,比如sysfs;
3.1
虚拟文件系统,简称VFS,作为内核子系统,为用户空间程序提供了文件系统相关的接口;
VFS使得用户可以直接使用open()、read()和write()这样的系统调用而无需考虑具体文件系统和实际物理介质;
write()调用将来自用户空间的数据流,首先通过VFS的通用系统调用,其次通过文件系统的特殊的写方法,最后写入物理介质中;
如果你不需要物理上连续的页,而仅仅需要虚拟地址上的连续的页,那么就使用vmalloc()(不过要记住vmalloc()相对kmalloc()来说,有一定的性能损失)。vmaloc()函数分配的内存虚地址是连续的,但它本身并不保证物理上的连续。这与用户空间的分配非常类似,它也是把物理内存块映射到连续的逻辑地址空间上;
如果你想从高端内存进行分配,就使用alloc_pages()。alloc_pages()函数返回一个指向struct page结构的指针,而不是一个指向某个逻辑地址的指针。因为高端内存很可能并没有被映射,因此,访问它的唯一方式就是通过相应的struct page结构。为了获得真正的指针,应该调用kmap(),把高端内存映射到内核的逻辑地址空间;
如果你需要连续的物理页,就可以使用某个低级页分配器或kmalloc()。传递给这些函数的两个最常用的标志是GFP_ATOMIC和GFP_KERNEL;GFP_ATOMIC表示不睡眠的高优先级分配。这是中断处理程序和不睡眠的代码段的需要。对于可以睡眠的代码,比如没有持自旋锁的进程上下文代码,则应该使用GFP_KERNEL获取所需的内存;这个标志表示,如果有必要,分配时可以睡眠;
在x86体系结构上,高于896MB的所有物理内存的范围大都是高端内存,它并不会永久的或自动地映射到内核地址空间;
在x86上,高端内存中的页被映射到3GB到4GB之间;
在任意一个函数中,你都必须尽量节省栈资源。这并不难,也没有什么固定的办法,你只需要在具体的函数中让所有局部变量(即所谓的自动变量)所占空间之和不要超过几百字节。在栈上进行大量静态分配,比如分配大型数组和大型结构,是很危险的。因此,进行动态分配是一种明智的选择;
每个进程都有两页的内核栈,因为32位和64位体系结构的页面大小分别为4KB和8KB,所以通常它们内核栈的大小分别是8KB和16KB;
2.28
TLB(translation lookaside buffer)是一种硬件缓冲区,很多体系结构用它来缓存虚拟地址到物理地址的映射关系。它极大地提高了系统的性能,因为大多数内存访问都要进行虚拟寻址;
在大多数情况下,只有硬件设备需要得到物理地址连续的内存。在很多体系结构上,硬件设备存在于内存管理单元以外,它根本不理解什么是虚拟地址。因此,硬件设备用到的任何内存区都必须是物理上连续的块,而不仅仅是虚拟地址连续的块。而仅供软件使用的内存块(例如,与进程相关的缓冲区)就可以使用只有虚拟地址连续的内存块。
vmalloc()函数的工作方式类似于kmalloc(),只不过前者分配的内存虚拟地址是连续的,而物理地址则无需连续。这也是用户空间分配函数的工作方式:由malloc()返回的函数在进程的虚拟地址空间内是连续的,但是,这 并不保证它们在物理RAM中也连续。
kmalloc()函数确保页在物理地址上是连续的(虚拟地址自然也是连续的)。
vmalloc()函数只确保页在虚拟地址空间内是连续的。
你不能给_get_free_pages()或kmalloc()指定_GFP_HIGHMEM,因为这两个函数返回的都是逻辑地址,而不是page结构,这两个函数分配的内存当前有可能还没有映射到内核的虚拟地址空间,因为,也可能根本就没有逻辑地址。只有alloc_pages()采恩那个分配高端内存。
2.27
在x86上,ZONE_NORMAL是从16MB到896MB的所有物理内存。
ZONE_DMA在x86上包含的页都在0~16MB的内存范围里。
在x86结构上,ZONE_HIGHMEM为高于896MB的所有物理内存。在其它体系结构上,由于所有内存都被直接映射,所以ZONE_HIGHMEM为空。
ZONE_HIGHMEM所在的内存就是所谓的高端内存(high memory),系统的其余内存就是所谓的低端内存(low memory)。
MMU,内存管理单元,管理内存并把虚拟地址转换为物理地址的硬件;
tasklet之间的同步,就是当两个不同类型的tasklet共享同一数据时;
两个相同类型的tasklet不允许同时执行,即使在不同的处理器上也不行;
需要注意的是,尽管操作处理函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相应的内存映射。
通常在系统调用发生时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有在此时它才会映射用户空间的内存;
tasklet本质上也是软中断,只不过同一个处理程序的多个实例不能在多个处理器上同时运行;
在2.6这个当前版本中,内核提供了三种不同形式的下半部实现机制:软中断、tasklet和工作队列;
2.25
在Linux中,由于上半部从来都只能通过中断处理程序实现,所以它和中断处理程序可以说是等价的;
理解为什么要让工作推后执行以及在什么时候推后执行非常关键。我们希望尽量减少中断处理程序中需要完成的工作量,因为在它运行的时候当前的中断线在所有处理器上都会被屏蔽。更糟糕的是如果一个处理程序是SA_INTERRUPT类型,它执行的时候会禁止所有本地中断(而且把本地中断线全局地屏蔽掉)。
下半部的任务就是处理与中断处理密切相关但中断处理程序本身不执行的工作;
记住,中断处理程序会异步执行,并且在最好的情况下它也会锁定当前的中断线。因此将中断处理程序持续执行的时间缩小到最小程度显得非常重要;
2.22
中断是一种由设备使用的硬件资源异步地向处理器发信号。实际上,中断就是由硬件来打断操作系统;
大多数现代硬件都通过中断与操作系统通信。对给定硬件进行管理的驱动程序注册中断处理程序,是为了响应并处理来自相关硬件的中断。中断过程所做的工作包括应答并重新设置硬件,从设备拷贝数据到内存以及反之,处理硬件请求,并发送新的硬件请求;
中断处理程序,即上半部;
通常情况下,你要检查自己是否处于进程上下文中。也就是说,你希望确保自己不在中断上下文中。这种情况很常见,因为代码要做一些像睡眠这样只能从进程上下文中做的事。如果in_interrupt()返回0,则此刻内核处于进程上下文;
根据规范,PCI设备必须支持中断线共享!
锁提供保护机制,防止来自其它处理器对共享数据的并发访问,而禁止中断提供保护机制,则是防止来自其他中断处理程序的并发访问;
procfs是一个虚拟文件系统,它只存在于内核内存,一般安装于/proc目录下。
进程上下文是一种内核所处的操作模式,此时内核代表进程执行——例如,执行系统调用或运行内核线程。
在进程上下文中,可以通过current宏关联当前进程。
此外,因为进程是以进程上下文的形式连接到内核中的,因此,在进程上下文可以睡眠,也可以调用调度程序。
执行一个中断处理程序或下半部时,内核处于中断上下文(interrupt context)中。
因为没有进程的背景,所以中断上下文不可以睡眠——否则又怎能再对它重新调度呢?
因此,不能从中断上下文中调用某些函数。如果一个函数睡眠,就不能在你的中断处理程序中使用它——这是对什么样的函数可以在中断处理程序中使用的限制;
中断上下文具有较为严格的时间限制,因为它打断了其它代码。中断上下文中的代码应当迅速简洁,尽量不要使用循环去处理繁重的工作。
有一点非常重要,请永远牢记:中断处理程序打断了其它的代码(甚至可能是打断了在其它中断线上的另一中断处理程序)。
正是因为这种异步执行的特性,所以所有的中断处理程序必须尽可能的迅速、简洁。尽量把工作从中断处理程序中分离出来,放在下半部来执行,因为下半部可以在更合适的时间运行。
2.20
注意,request_irq()函数可能会睡眠,因此,不能在中断上下文或其它不允许阻塞的代码中调用该函数;
中断处理程序是管理硬件的驱动程序的组成部分;
如果设备使用中断(大部分设备如此),那么相应的驱动程序就注册一个中断处理程序;
中断处理程序通常不是和特定设备关联,而是和特定中断关联的;
一个设备的中断处理程序是它设备驱动程序(driver)的一部分——设备驱动程序是用于对设备进行管理的内核代码;
中断:由硬件产生的异步中断;
异常:由处理器本身产生的同步中断;
从物理学的角度看,中断是一种电信号,由硬件设备生成,并直接送入中断控制器的输入引脚。然后再由中断控制器向处理器发送相应的信号。
处理器一经检测到此信号,便中断自己的当前工作转而处理中断。
中断使得硬件得以与处理器进行通信。
中断本质上是一种特殊的电信号,由硬件设备发向处理器。处理器接收到中断后,会马上向操作系统反映此信号的到来,然后就由OS负责处理这些新到来的数据。
执行系统调用的连锁反应:
陷入内核,传递系统调用号和参数,执行正确的系统调用函数,并把返回值带回用户空间;
系统调用与库函数和应用程序接口(API)有怎样的关系;
通常,系统调用靠C库支持。用户程序通过包含标准头文件并和C库链接,就可以使用系统调用(或者调用库函数,再由库函数实际调用)。
中断处理程序不能休眠,这使得中断处理程序所能进行的操作较之运行在进程上下文中的系统调用所能进行的操作受到了极大的限制;
2.18
在Linux中,系统调用是用户空间访问内核的唯一手段;除异常和陷入外,它们是内核唯一的合法入口;
内核提供给运行进程的主要接口----系统调用。
上下文切换,也就是从一个可执行进程切换到另一个可执行进程,由定义在kernel/sched.c中的context_switch()函数负责处理。
Linux的调度程序为对称多处理系统的每个处理器准备了单独的可执行队列和锁。
也就是说,每个处理器拥有一个自己的进程链表,而它只对属于自己的这些进程进行调度操作。
休眠通过等待队列进行处理。等待队列是由等待某些时间发生的进程组成的简单链表。
内核用wake_queue_heat_t来代表等待队列。
2.17
如果一个进程的大部分时间都在休眠,那么它就是I/O消耗型的。
如果一个进程执行的时间比休眠的时间长,那么它就是处理器消耗型的。
选定下一个进程并切换到它去执行是通过schedule()函数实现的;
自旋锁用于防止多个任务同时对可执行队列进行操作;
调度程序中最基本的数据结构是运行队列(runqueue);
可执行队列是给定处理器上的可执行进程的链表,每个处理器一个;
LInux的调度程序定义于kernel/sched.c中;
时间片是一个数值,它表明进程在被抢占前所能持续运行的时间;
进程并不是一定非要一次就用完它所有的时间片;
调度程序总是选择时间片未用尽,并且优先级最高的进程运行;
2.16
所有的进程都是PID为1的init进程的后代。内核在系统启动的最后的阶段启动init进程。该进程读取系统的初始化脚本(initscript)并执行其他的相关程序,最终完成系统启动的整个过程;
进程的另一个名字是任务(task)。Linux内核通常把进程也叫做任务。
内核调度的对象是线程,而不是进程;
存放全局变量的数据段;
一段可执行程序代码,代码段(text section);
2.15
认真阅读源码非常必要,Linux系统代码的开放性其实是弥足珍贵的,不要无动于衷地将它搁置一边,从而浪费了打好资源。
只有动手写代码才能真正融会贯通。
Linux克隆了Unix,但Linux不是Unix。
尽管Linux借鉴了Unix的许多设计并且实现了Unix的API,但Linux没有像其他Unix变种那样直接使用Unix的源代码;
通常,一个内核由负责响应中断的中断服务程序,负责管理多个进程从而分享处理器时间的调度程序,负责管理进程地址空间的内存管理程序和网络、进程间通信等系统服务程序共同组成;
内核开发者通常把那些对时间要求比较高,而本身长度又比较短的函数定义成内联函数;
2.13
driver routines registered with VFS through the file_operations structures;
2.2
distinguish pages(groups of data) from page frames(physical address in main memory).
This allows the same page to be stored in a page frame, then saved to disk and later
reloaded in a different page frame.This is the basic ingredient of the virtual memory mechanism.
术语“页”既指一组线性地址(即虚拟地址),又指包含在这组地址中的数据;
把线性地址(虚拟地址)映射到物理地址的数据结构称为页表(page table);
2.1
80*86微处理器怎样进行芯片级寻址,linux又是如何利用寻址硬件的;
本文深入探讨了Linux内核的设计原理和技术细节,包括进程管理、内存管理、文件系统、设备驱动等多个方面。揭示了内核如何高效管理和调度系统资源。
1225

被折叠的 条评论
为什么被折叠?



