Linux内核阅读笔记 0.11 一

什么是写时复制?COW copy on write
写入时复制是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这个过程对其他的调用者是透明的(transparently)。此作法的主要优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作是可以共享同一份资源。

什么是内联函数?static inline 和 extern inline 的含义
它的引入使得编程者只关心函数的功能和使用方法,而不必关心函数功能的具体实现;函数的引入可以减少程序的目标代码,实现程序代码和数据的共享。但是,函数调用也会带来降低效率的问题,因为调用函数实际上将程序执行顺序转移到函数所存放在内存中某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去前要保护现场并记忆执行的地址,转回后先要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。特别是对于一些函数体代码不是很大,但又频繁地被调用的函数来讲,解决其效率问题更为重要。引入内联函数实际上就是为了解决这一问题。
在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替换。显然,这种做法不会产生转去转回的问题,但是由于在编译时将函数休中的代码被替代到程序中,
因此会增加目标
程序代码量,进而增加空间开销,而在时间代销上不象函数调用时那么大,可见它是以目标代码的增加为代价来换取时间的节省。
1.内联函数可减少cpu的系统开销,并且程序的整体速度将加快,但当内联函数很大时,会有相反的作用,因此一般比较小的函数才使用内联函数.
2.有两种内联函数的声明方法,一种是在函数前使用inline关见字,另一种是在类的内部定义函数的代码,这样的函数将自动转换为内联函数,而且没必要将inline放在函数前面.
3.内联是一种对编译器的请求,下面这些情况会阻止编译器服从这项请求.
  如果函数中包含有循环,switch或goto语句,递归函数,含有static的函数.
extern inline表示该函数是已声明过的了.由于函数本身可以声明多次,所以extern对函数的影响仅仅把函数的隐藏属性显式化了.
extern 对于非函数的对象是有用的,因为对象声明时会带来内存的分配,而用 extern就表示该对象已经声明过了,不用再分配内存.

_syscall0
添加新的系统调用_syscall0(int, mysyscall)
EAX寄存器以称为累加器,AX寄存器是算术运算的主要寄存器,所有的输入、输出只使用AL或AX人作为数据寄存器。在80386及其以上的微处理器中,EAX寄存器可以用来存储单元的偏移地址。

#include <linux/unistd.h>
_syscall0(int,mysyscall) /* 注意这里没有分号*/
int main()
{
mysyscall();
}

好,由于有了_syscall0 这个宏,mysyscall 将得到定义。但是现在系统会去找系统调用号,以放入eax。所以,接下来我们定义系统调用号。

添加系统调用号
系统调用号在文件unistd.h里面定义。这个文件可能在你的系统上会有两个版本:一个
是C库文件版本,出现的地方是在/usr/include/unistd.h和/usr/include/asm/unistd.h;另外还有
一个版本是内核自己的unistd.h,出现的地方是在你解压出来的2.4.18 内核代码的对应位置
(比如/usr/src/linux/include/linux/unistd.h和/usr/include/asm-i386/unistd.h)。当然,也有可能
这个C 库文件只是一个到对应内核文件的连接。
现在,你要做的就是在文件unistd.h中添加我
们的系统调用号:__NR_mysyscall,如下所示:
include/asm-i386/unistd.h
/usr/include/asm/unistd.h

243 #define __NR_lremovexattr 236
244 #define __NR_fremovexattr 237
245 #define __NR_mysyscall 238 /* 添加的系统调用 */

添加系统调用号之后,系统才能根据这个号,作为索引,去找syscall_table中的相应表项。
所以说,我们接下来的一步就是:
在系统调用表中添加相应表项
我们前面讲过,系统调用处理程序(system_call)会根据eax 中的索引到系统调用表
(sys_call_table)中去寻找相应的表项。所以,我们必须在那里添加我们自己的一个值。
arch/i386/kernel/entry.S

398 ENTRY(sys_call_table)
399 .long SYMBOL_NAME(sys_ni_syscall)
400 .long SYMBOL_NAME(sys_exit)
401 .long SYMBOL_NAME(sys_fork)
402 .long SYMBOL_NAME(sys_read)
403 .long SYMBOL_NAME(sys_write)
……
……
634 .long SYMBOL_NAME(sys_ni_syscall)
635 .long SYMBOL_NAME(sys_ni_syscall)
636 .long SYMBOL_NAME(sys_ni_syscall)
637 .long SYMBOL_NAME(sys_mysyscall)
638
639 .rept NR_syscalls-(.-sys_call_table)/4
640 .long SYMBOL_NAME(sys_ni_syscall)
641 .endr

到现在为止,系统已经能够正确地找到并且调用sys_mysyscall。剩下的就只有一件事情,那
就是sys_mysyscall的实现。
sys_mysyscall 的实现
我们把这一小段程序添加在kernel/sys.c 里面。在这里,我们没有在kernel 目录下另外
添加自己的一个文件,这样做的目的是为了简单,而且不用修改Makefile,省去不必要的麻
烦。

asmlinkage int sys_mysyscall(void)
{
current->uid = current->euid = current->suid = current->fsuid = 0;
return 0;
}

这个系统调用中,把标志进程身份的几个变量uid、euid、suid和fsuid都设为0。
到这里为止,我们所要做的添加一个新的系统调用的所有工作就完成了,是不是非常简
单?的确如此。因为Linux 内核结构的层次性还是非常清楚的,这就使得每一个开发者可以
把精力放在怎么样实现具体的功能上,而不用在一些接口函数上伤脑筋。
测试

#include <linux/unistd.h>
_syscall0(int,mysyscall) /* 注意这里没有分号*/
int main()
{
mysyscall(); /* 这个系统调用的作用是使得自己的uid为0 */
printf(“em…, this is my uid: %d. \n”, getuid());
}

全局描述表(GDT Global Descriptor Table):在保护模式下一个重要的数据结构。
局部描述符表(LDT— Local Descriptor Table):是保护模式下存储器寻址的一种数据表,它包含了与某个任务相关联的段描述符,在设计操作系统时,通常每个任务有一个独立的LDT。LDT提供了将一任务的代码段、数据段与操作系统的其余部分相隔离的机制。
LDT的定位与GDT不同,LDT的段基址与段限由LDT描述符表示,该描述符同一般的描述符一样存放在全局描述符表中,因此首先要从GDT中找到LDT描述符,80386微处理器中有一个局部描述符表寄存器 LDTR,这是一个16位寄存器,LDTR中存放一个被称为“段选择符”(Segment Selector)的16位数,段选择符用来在GDT中寻找LDT描述符。
段描述符是GDT(全局描述表)和LDT表(局部描述符表)中的一个数据结构项,用于向处理器提供有关一个段的位置和大小信息以及访问控制的状态信息
保护模式:设计用来增强多工和系统稳定度,像是 内存保护,分页 系统,以及硬件支援的 虚拟内存。
另外一种286和其之后CPU的运行模式是实模式,一种向前兼容且关闭了保护模式这些特性的CPU运行模式。用来让新的芯片可以运行旧的软件。依照设计的规格,所有的x86 CPU都是在实模式下开机,来确保传统操作系统的向前兼容性。在任何保护模式的特性可用前,他们必须要由某些程序手动地切换到保护模式。在现今的计算机,这种切换通常是由操作系统在开机时候必须完成的第一件任务的一个。它也可能当CPU在保护模式下运行时,使用虚拟86模式来运行设计运行在实模式下的代码。

程序不可见部分:就是cache
任务状态段:TSS是指在操作系统进程管理的过程中,任务(进程)切换时的任务现场信息。 在任务切换过程中,首先,处理器中各寄存器的当前值被自动保存到TR(任务寄存器)所指定的TSS中;然后,下一任务的TSS的选择子被装入TR;最后,从TR所指定的TSS中取出各寄存器的值送到处理器的各寄存器中。由此可见,通过在TSS中保存任务现场各寄存器状态的完整映象,实现任务的切换。

从实模式到保护模式的过程针对80386处理器
1.初始化IDT
2.初始化GDT
3.PE位置1
4.JMP 跳转
5.选择子

虚拟8086模式
特殊的模式。时间片的技术,运行多个任务。

Linux系统的体系结构
用户应用程序
操作系统服务
操作系统内核(和硬件交互)
硬件系统

主程序》系统服务》支持函数

单内核模式,速度快,但是层次结构不强。

Linux内核几个重要的模块:
1.内存管理模块
2.进程调度模块
3.文件系统模块
4.进程间通信模块
5.网络接口模块

发布了27 篇原创文章 · 获赞 20 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_33479881/article/details/103160163