陈民禾 原创作品转载请注明出处 ——《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一.复习上周内容
上周主要学习了内核的启动过程可以简单地这么来看:start_kernel从内核一启动的时候它会一直存在,这个就是0号进程,idle就是一个while0,一直在循环着,当系统没有进程需要执行的时候就调度到idle进程,我们在windows系统上会经常见到,叫做system idle,这是一个一直会存在的0号进程,然后呢就是0号进程创建了1号进程,这个init_process是我们的1号进程也就是第一个用户态进程,也就是它默认的就是根目录下的程序,也就是常会找默认路径下的程序来作为1号进程,1号进程接下来还创建了kthreadd来管理内核的一些线程,这样整个程序就启动起来了。
二.内核态、用户态、中断等概念的介绍
用户态和内核态的区分:
现代计算机机中都有几种不同的指令级别,在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态,而在相应的低级别执行状态下,代码的掌控范围会受到限制,只能在对应级别允许的范围内活动。举例:Intrel x86 CPU有四种不同的执行级别0-3,Linux只使用了其中的0级和3级来分别表示内核态和用户态。操作系统让系统本身更为稳定的方式,这样程序员自己写的用户态代码很难把整个系统都给搞崩溃,内核的代码经过仔细的分析有专业的人员写的代码会更加健壮一些,整个程序会更加稳定一些,注意:这里所说的地址空间是逻辑地址而不是物理地址。
用户态和内核态的很显著的区分就是:CS和EIP, CS寄存器的最低两位表明了当前代码的特权级别;CPU每条指令的读取都是通过CS:EIP这两个寄存器:其中CS是代码段选择寄存器,EIP是偏移量寄存器,上述判断由硬件完成。一般来说在Linux中,地址空间是一个显著的标志:0xc0000000以上的地址空间只能在内核态下访问,0xc00000000-0xbfffffff的地址空间在两种状态下都可以访问。
中断处理是从用户态进入到内核态的主要的方式:
#define SAVE_ALL RESTORE_ALL
"cld\n\t"\ popl %ebx;
"pushl %es\n\t"\ popl %ecx;
"pushl %ds\n\t"\ popl %ebx;
"pushl %eax\n\t"\ popl %edx;
"pushl %ebp\n\t"\ popl %esi;
"pushl %edi\n\t"\ popl %edi;
"pushl %esi\n\t"\ popl %ebp;
"pushl %edx\n\t"\ popl %eax;
"pushl %ecx\n\t"\ popl %ds;
"pushl %ebx\n\t"\ popl %es;
"movl $" STR(_KERNEL_DS)",%edx\n\t"\ addl $4,%esp;
"movl %edx,%ds\n\t"\ iret;
"movl %edx,%es\n\t"
iret指令与中断信号(包括int指令),发生时的CPU的动作正好相反。
仔细分析一下中断处理的完整过程:
SAVE_ALL -...//内核代码,完成中断服务,可能会发生进程调度 RESTOER_ALL //完成之后再返回到原来的状态 iret-pop cs:eip/ss:eip/eflag from kernel stack
三.系统调用概述
#include<stdio.h> #include<unistd.h> int main(void) { write(1,"hello world!5124\n",13); return 0; }
下面是我的命令行内容:
其中,write有三个参数,第一个是表示写到终端屏幕上,1可以认为是屏幕的代号,第二个参数是写的内容,我是把hello world!写到屏幕上,并换行,第三个参数是写入的字符串长度,长度要大于等于要输出的字符串长度,否则只能输出字符串的一部分。程序执行结果如下:
然后是把这段代码转写为嵌入式汇编,嵌入式汇编的格式如下:_asm_( 汇编语句模块: 输出部分:函数调用时候的参数 输入部分:函数调用时候的参数 破坏描述部分): 即格式为asm("statements":output_regs:input_regs:clobbered_regs); 可以看成是一个函数,有时候可以加一个_volatile_来选择让编译器优化或者不让编译器优化。
代码如下:
#include<stdio.h> #include<unistd.h> int main() { int a; char *ch="hello world!\n"; asm volatile( "movl $0x4,%%eax\n\t" "movl $0x1,%%ebx\n\t" "movl $0x1,%%ecx\n\t" "movl $0xd,%%edx\n\t" "int $0x80\n\t" "movl %%eax,%0\n\t" :"=m"(a) :"s"(ch) ); return 0; }
write系统调用有三个参数,分别是:写入的位置,内容和长度,所以转化为汇编对应的寄存器为eax(系统调用号为4),ebx(参数),ecx(输出位置),edx(参数长度)