标签:
1、内存分布示意图
再附一张,描述符表。
主要说一下全局描述符中的内容:
每一项都是8个字节。
第一项为全0,没有被使用。
第二项为存储段描述符,DT=1,DPL=00,代码段描述符,基地址是0,段界限是16MB。
第三项为存储段描述符,DT=1,DPL=00,数据段描述符,基地址是0,段界限是16MB。
第四项为全0,没有被使用。
第五项为系统段描述符,DT=0,DPL=00,进程0的任务状态段,基地址指向进程0的TSS的地址,段界限是进程0的TSS的长度。
第六项为系统段描述符,DT=0,DPL=00,进程0的局部描述符表,基地址指向进程0的LDT的地址,段界限是进程0的LDT的长度。
第七项 以后类推 进程1 进程2 ......
最后形成如下图:
中断描述符表如下:
每一项都是门描述符,分为三种,一是中断门,TYPE为110,DPL为00。二是陷阱门,TYPE为111,DPL为00。三是系统门,TYPE为111,DPL为11。seletor都是0x08,选择了代码段描述符,基地址是0,段界限是16MB。偏移就是具体中断处理函数的偏移。
进程0的LDT如下:
第一项为全零。
第二项为存储段描述符,DT=1,DPL=11,代码段描述符,基地址是0,段界限是640KB。
第三项为存储段描述符,DT=1,DPL=11,数据段描述符,基地址是0,段界限是640KB。
进程1的LDT如下:
第一项为全零。
第二项为存储段描述符,DT=1,DPL=11,代码段描述符,基地址是64MB,段界限是640KB。
第三项为存储段描述符,DT=1,DPL=11,数据段描述符,基地址是64MB,段界限是640KB。
进程2的LDT如下:
第一项为全零。
第二项为存储段描述符,DT=1,DPL=11,代码段描述符,基地址是128MB,段界限是640KB。
第三项为存储段描述符,DT=1,DPL=11,数据段描述符,基地址是128MB,段界限是640KB。
以此类推。
但进程2后来执行了如下代码:
void init(void) { int pid,i; ... if (!(pid=fork())) {//进程1创建进程2 close(0); if (open("/etc/rc",O_RDONLY,0)) _exit(1); execve("/bin/sh",argv_rc,envp_rc); _exit(2); } if (pid>0) while (pid != wait(&i)) /* nothing */; ... }
那么:shell的LDT如下:
第一项为全零。
第二项为存储段描述符,DT=1,DPL=11,代码段描述符,基地址是128MB,段界限是shell程序的实际长度。
第三项为存储段描述符,DT=1,DPL=11,数据段描述符,基地址是128MB,段界限是64MB(这样页目录项会占从32~48的位置)。
2、下面介绍80386寄存器
内核态,CS选择器 TI=0,表示从GDT表中选择,RPL=00,内核态,描述符索引是1,选择GDT表中第二个代码段描述符。
内核态,SS,DS,ES,FS,GS选择器 TI=0,表示从GDT表中选择,RPL=00,内核态,描述符索引是2,选择GDT表中第三个数据段描述符。
用户态,CS选择器 TI=1,表示从LDT表中选择,RPL=11,用户态,描述符索引是1,选择LDT表中第二个代码段描述符。
用户态,SS,DS,ES,FS,GS选择器 TI=1,表示从LDT表中选择,RPL=11,用户态,描述符索引是2,选择LDT表中第三个数据段描述符。
LDTR选择器,TI=1,表示从GDT表中选择,RPL=00,内核态,描述符索引和进程有关,进程0的描述符索引是3,进程1的描述符索引是5,选择GDT表中第四个,或者第六个系统段描述符。依次类推。
TR选择器,TI=1,表示从GDT表中选择,RPL=00,内核态,描述符索引和进程有关,进程0的描述符索引是4,进程1的描述符索引是6,选择GDT表中第五个,或者第七个系统段描述符。依次类推。
描述符高速缓存寄存器和LDTR高速缓存寄存器和TR高速缓存寄存器,0-31存放段首地址,0-19存放段界限,0-11存放段属性。就是分别对应上面取得的存储段描述符和系统段描述符(也是64位)。
GDTR,0~31位存放的是内存分配图中GDT(全局描述符表)的基地址,0~15位存放的是内存分配图中GDT(全局描述符表)的界限。
IDTR,0~31位存放的是内存分配图中IDT(中断描述符表)的基地址,0~15位存放的是内存分配图中IDT(中断描述符表)的界限。
3、特权级切换的本质:
中断int 0x80从特权级3进入特权级0,并把ss,esp,eflags,cs,eip等寄存器保存在特权级0的堆栈中(TSS的esp0,ss0),iret从特权级0返回特权级3。
由于seletor都是0x08,表示是内核态的cs选择器,从GDT中取描述符,所以中断处理程序已经处于0特权级,内核态。压栈是ss也0x10,后来ds,es,fs,gs也变成了0x10。
所以特权级变化的本质是,cs,ds,es,fs,gs,ss的不同,特权级0从GDT中取得描述符,前面这些寄存器后3位为000,描述符特权级为00;特权级3从LDT中取得描述符,前面这些寄存器后3位为111,描述符特权级为11。
4、进程切换的本质:
把当前的寄存器状态保存在当前进程的TSS中,把要切换的进程的寄存器恢复成要切换的进程的TSS中的内容。要着重理解LDTR选择器,TR选择器。LDTR选择器在GDT表中找出了当前进程的LDTR高速缓存,也就是LDT表的基地址和界限。TR选择器在GDT表中找出了当前进程的TR高速缓存,也就是TSS表的基地址和界限。
5、中断的本质:
中断和特权级切换的过程类似,只是中断可能发生在用户态或者内核态,而特权级切换只发生在用户态。当中断发生在内核态时,压入特权级0堆栈的cs,esp,eflags,cs,eips是内核态的寄存器值。
6、创建进程的本质:
获取了一页内存如下图:
(1) task_struct是复制父进程的数据得来的。会修改pid,father,tss中数据eip,cs指向的是父进程将要执行的下行代码。ldt中数据直接继承下来,后面还要修改。
(2)设置子进程ldt代码段和数据段的基地址为nr * 64MB,段界限为640KB。
(3)设置进程的页目录项,进程0是第一项;进程1是第16项,进程2是第32项。先获取一页内存,再复制父进程的页表项到这个内存中。这样父子进程共享实际内存。
(4)子进程也继承了父进程的pwd,root,filp。计数加1。
(5)设置tast_stuct中的tss,ldt在gdt中系统段描述符。把tss,ldt的基地址和段界限放入gdt中。
父进程fork返回非0,子进程fork返回0,但都是执行fork后面的代码,可以通过if语句来区分。
7、执行程序的本质:
首先释放原进程页目录项和页表项,然后重新建立起页目录项和页表项,最后返回到用户空间执行。具体参考通过进程2加载shell进程,详解execve。8、进程调度的本质:
一种是时钟中断,每10ms一次,但是只有发生时钟时,处于用户态,才会产生进程切换。
一种是进程在内核态主动调用schedue()。
9、schedue函数:
找出counter最大的就绪进程运行。如果没有就绪的进程就切换到进程0怠速。如果有就绪的进程但counter值都为0,那么就重新分配时间片。
10、进程状态TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE。
TASK_INTERRUPTIBLE如果当前进程接收到信号,那么schedue时会把这个进程变成TASK_RUN。但是TASK_UNINTERRUPTIBLE,是不会理睬信号的。
TASK_UNINTERRUPTIBLE只能由wake_up唤醒,变成TASK_RUN。
一般在pause时会把进程设置为TASK_INTERRUPTIBLE。在sleep_on时会把进程设置为TASK_UNINTERRUPTIBLE。
11、信号的本质
发送信号:就是在目标进程的signal,对应的设置为1。
接收信号:一种在系统调用返回之前检测当前进程是否接收到信号;另一种方式是时钟中断发生后,其中断服务程序执行结束之前,检测当前进程是否接收到信号。
会根据signal检测是否接收到信号,并根据signal找到current->sigaction + signr - 1找到对应的信号处理函数。
从系统调用返回后先执行信号处理函数,然后再转到发生系统调用的下一条指令执行。参考Linux内核设计的艺术-进程间通信-信号。
12、管道的本质
管道的本质是操作系统在内存中为每个管道开辟一页内存,给这一页内存赋予了文件的属性。这一页内存由两个进程共享。
inode->i_size=get_free_page();
f[0]->f_inode = f[1]->f_inode = inode;
13、put_page的本质:
unsigned long put_page(unsigned long page,unsigned long address)
address是线性地址,page指向页面的地址。
执行完函数后,会建立从线性地址到页面的地址的映射。也就是说建立了页目录项和页表项。然后线性地址-->分页机制-->实际物理页面的地址。
标签:
原文地址:http://blog.csdn.net/jltxgcy/article/details/43705619