> 文章列表 > Linux0.11 系统调用进程创建与执行(九)

Linux0.11 系统调用进程创建与执行(九)

Linux0.11 系统调用进程创建与执行(九)

Linux0.11 系统调用进程创建与执行(九)

系列文章目录

Linux 0.11启动过程分析(一)
Linux 0.11 fork 函数(二)
Linux0.11 缺页处理(三)
Linux0.11 根文件系统挂载(四)
Linux0.11 文件打开open函数(五)
Linux0.11 execve函数(六)
Linux0.11 80X86知识(七)
Linux0.11 内核体系结构(八)
Linux0.11 系统调用进程创建与执行(九)


文章目录

  • 系列文章目录
  • 前提回顾
    • 内存分布
    • 堆栈信息
    • 寄存器信息
      • 段选择符
      • 通用寄存器
      • task[0] 信息
        • ldt
        • tss
  • 一、环境初始化
  • 二、move_to_user_mode
      • 1、寄存器
        • ldt:
      • 2、分析
        • 2.1 段选择符
        • 2.2 段描述符
        • 2.3 Task0 处于用户态
          • CS
          • 其他段寄存器 SS、DS、ES、FS、GS
        • 2.4 Task0 处于内核态
          • CS
          • 其他段寄存器 SS、DS、ES、FS、GS
  • 三、调用 fork 创建进程 1(init)
    • 1、中断描述符
    • 2、 system_call 函数
      • 2.1 copy_process 函数
      • 2.2 设置进程 1 的分页管理
    • 3、系统调用返回
    • 4、寄存器信息
      • 4.1 进程 0
        • 4.1.1 内核态信息
        • 4.1.2 用户态信息
        • 4.1.3 task_struct 中其它字段
      • 4.2 进程 1
        • 4.2.1 初始化时信息:
          • ldt
          • tss
        • 4.2.2 用户态信息
          • 通用寄存器和段寄存器
        • 4.2.3 内核态信息
          • 通用寄存器和段寄存器
        • 4.2.4 task_struct 中其它字段
  • 四、进程 2
    • 1、调用 fork 创建进程 2
      • 通用寄存器和段寄存器
      • ldt
      • tss
      • task_struct 中其它字段
    • 2、进程 2 执行 execve 函数
      • ldt
      • tss
      • task_struct 中其它字段
  • 中断
    • task0
    • task1

前提回顾

    Linux 系统经历 BIOSbootsect.s、setup.s、head.s 一系列执行后,    其从实模式切换到了 32 位保护模式,此时即将运行 init/main.c 中的 main 函数。

内存分布

此时其内存分布如下:
Linux0.11 系统调用进程创建与执行(九)
Linux0.11 系统调用进程创建与执行(九)

堆栈信息

堆栈信息如下图:
Linux0.11 系统调用进程创建与执行(九)

寄存器信息

段选择符

Linux0.11 系统调用进程创建与执行(九)

通用寄存器

Linux0.11 系统调用进程创建与执行(九)

task[0] 信息

struct task_struct {// .../* 本任务的局部表描述符。 0 空, 1 代码段 cs, 2 数据段和堆栈段 ds&ss */struct desc_struct ldt[3];/* 本进程的任务状态段信息结构 */struct tss_struct tss;
};

task[0] 初始化:

#define INIT_TASK \\
/* state etc */	{ 0,15,15, \\
/* signals */	0,{{},},0, \\
/* ec,brk... */	0,0,0,0,0,0, \\
/* pid etc.. */	0,-1,0,0,0, \\
/* uid etc */	0,0,0,0,0,0, \\
/* alarm */	0,0,0,0,0,0, \\
/* math */	0, \\
/* fs info */	-1,0022,NULL,NULL,NULL,0, \\
/* filp */	{NULL,}, \\{ \\{0,0}, \\
/* ldt */	{0x9f,0xc0fa00}, \\{0x9f,0xc0f200}, \\}, \\
/*tss*/	{0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\\0,0,0,0,0,0,0,0, \\0,0,0x17,0x17,0x17,0x17,0x17,0x17, \\_LDT(0),0x80000000, \\{} \\}, \\
}

ldt

Linux0.11 系统调用进程创建与执行(九)

tss

Linux0.11 系统调用进程创建与执行(九)


一、环境初始化

void main(void) /* This really IS void, no error here. */
{               /* The startup routine assumes (well, ...) this *// Interrupts are still disabled. Do necessary setups, then* enable them*/ROOT_DEV = ORIG_ROOT_DEV;drive_info = DRIVE_INFO;memory_end = (1 << 20) + (EXT_MEM_K << 10);memory_end &= 0xfffff000;if (memory_end > 16 * 1024 * 1024)memory_end = 16 * 1024 * 1024;if (memory_end > 12 * 1024 * 1024)buffer_memory_end = 4 * 1024 * 1024;else if (memory_end > 6 * 1024 * 1024)buffer_memory_end = 2 * 1024 * 1024;elsebuffer_memory_end = 1 * 1024 * 1024;main_memory_start = buffer_memory_end;
#ifdef RAMDISKmain_memory_start += rd_init(main_memory_start, RAMDISK * 1024);
#endifmem_init(main_memory_start, memory_end);trap_init();blk_dev_init();chr_dev_init();tty_init();time_init();sched_init();buffer_init(buffer_memory_end);hd_init();floppy_init();sti();move_to_user_mode();if (!fork()) { /* we count on this going ok */init();}/   NOTE!!   For any other task 'pause()' would mean we have to get a* signal to awaken, but task0 is the sole exception (see 'schedule()')* as task 0 gets activated at every idle moment (when no other tasks* can run). For task0 'pause()' just means we go check if some other* task can run, and if not we return here.*/for (;;)pause();
}

    代码执行到 move_to_user_mode 函数之前,其进行了一系列初始化操作。跟踪发现除了通用寄存器发生变化外,段寄存器,task[0]ldttss 都未发生变化。

以下为通用寄存器信息:
Linux0.11 系统调用进程创建与执行(九)

    执行完 move_to_user_mode 函数后,程序由内核态进入用户态(任务 0 的用户态)。

二、move_to_user_mode

   move_to_user_mode 函数把进程 0 由内核态转成用户态。其代码如下:

#define move_to_user_mode() \\		// 模仿中断硬件压栈,顺序是 ss、esp、eflags、cs、eip
__asm__ ("movl %%esp,%%eax\\n\\t" \\	// 保存堆栈指针 esp 到 eax 寄存器中"pushl $0x17\\n\\t" \\				// 堆栈段选择符 ss 入栈, 0x17 即二进制的 10111(特权级3、LDT、数据段)"pushl %%eax\\n\\t" \\				// 堆栈指针 esp 入栈"pushfl\\n\\t" \\					// 标志进村器 eflags 入栈"pushl $0x0f\\n\\t" \\				// Task0 代码段选择符 cs 入栈, 0x0f 即 111 (特权级3、LDT、代码段)"pushl $1f\\n\\t" \\				// 将下面标号 1 的偏移地址 eip 入栈"iret\\n" \\						// 出栈恢复现场,翻转特权级从 0 到 3,中断返回,会跳转到下面标号 1处"1:\\tmovl $0x17,%%eax\\n\\t" \\	// 此时开始执行进程 0 。"movw %%ax,%%ds\\n\\t" \\			// 初始化段寄存器指向 task0 的局部表的数据段"movw %%ax,%%es\\n\\t" \\			// 下面的代码使 es、fs、gs 与 ds 一致"movw %%ax,%%fs\\n\\t" \\"movw %%ax,%%gs" \\:::"ax")

1、寄存器

此时寄存器的信息如下:
Linux0.11 系统调用进程创建与执行(九)

ldt:

Linux0.11 系统调用进程创建与执行(九)

tss 未发生变化

2、分析

typedef struct desc_struct {unsigned long a,b;
} desc_table[256];

以下为 GDT、LDT、TSS 间的关系:
Linux0.11 系统调用进程创建与执行(九)
值的形式为:b:a,其位于地址 0x5cb8 处。

0 1 2 3 4 5
NULL 内核CS 内核DS NULL TSS0 LDT0
0:0 C09A00:FFF C09300:FFF 0:0 8B01:F4480068 8201:F4300068
6 7 8 9
TSS1 LDT1 TSS2 LDT2
0:0 0:0 0:0 0:0

Linux0.11 系统调用进程创建与执行(九)

2.1 段选择符

段选择符结构如下:
Linux0.11 系统调用进程创建与执行(九)

2.2 段描述符

Linux0.11 系统调用进程创建与执行(九)

2.3 Task0 处于用户态

CS

    此时段寄存器 CS0x0F。指定了 LDT 中具有 RPL=3 的段 1,其索引字段值是 1,TI 位是 1,指定 LDT 表。其指向Task0的局部描述符,其值为:C0FA00:0x9F
Linux0.11 系统调用进程创建与执行(九)

    备注:GDT 中第 5 个值 LDT0 存储了 LDT 描述符地址,其值为:8201:F4300068,其段基址为 0x1F430,段限长为 104 个字节。段基址正好指向 task[0] 中成员变量 ldt 的地址。而 task[0].ldt[1]LDT 描述符,其值正是上文说的 C0FA00:0x9F
Linux0.11 系统调用进程创建与执行(九)

其他段寄存器 SS、DS、ES、FS、GS

    此时其他段寄存器 SSDS、ES、FS、GS 值为 0x17。指定了 LDT 中具有 RPL=3 的段 2,其索引字段值是 2,TI 位是 1,指定 LDT 表。其指向Task0的局部描述符,其值为:C0F300:0x9F
Linux0.11 系统调用进程创建与执行(九)
    备注:GDT 中第 4 个值 TSS0 存储了 TSS 描述符地址,其值为:8B01:F4480068,其段基址为 0x1F448,段限长为 104 个字节。段基址正好指向 task[0] 中成员变量 tss 的地址。而 task[0].tssTSS 描述符,其值正是上文说的 C0F300:0x9F
Linux0.11 系统调用进程创建与执行(九)


2.4 Task0 处于内核态

CS

    此时段寄存器 CS0x08。其指定了 GDT 中具有 RPL=0 的段 1,其索引字段值是 1,TI 位是 0,指定 GDT 表。其指向的描述符值为:0xC09A00:FFF
Linux0.11 系统调用进程创建与执行(九)

其他段寄存器 SS、DS、ES、FS、GS

    此时其他段寄存器 SSDS、ES、FS、GS 值为 0x10。其指定了 GDT 中具有 RPL=0 的段 2,其索引字段值是 2,TI 位是 0,指定 GDT 表。其指向的描述符值为:0xC09300:FFF
Linux0.11 系统调用进程创建与执行(九)

三、调用 fork 创建进程 1(init)

    fork 函数是个系统调用,此处由进程 0 在用户态调用 fork 函数来创建进程 1fork 函数触发的中断,由 kernel/system_call.ssystem_call 函数响应。fork 函数定义如下(可参考 三、fork 函数定义 ):

static inline int fork(void) { long __res; __asm__ volatile ("int $0x80" 		// 调用系统中断 0x80: "=a" (__res) 					// 返回值 => eax(__res): "0" (2)); if (__res >= 0) return (type) __res; errno = -__res; return -1; 
}

1、中断描述符

   中断描述符表位于 0x000054b8 处,系统调用中断号为 0x80(128),idt[128] 其值为:EF00:87632。
Linux0.11 系统调用进程创建与执行(九)

IDT 对应的描述符说明如下(可参考 中断描述符表):
Linux0.11 系统调用进程创建与执行(九)
   由上可以看出,这是一个陷阱门。其段选择符为:0x08,其偏移值为:0x7632(30258)。
结合下图的调用说明,
Linux0.11 系统调用进程创建与执行(九)
可知:段选择符为 0x08 指向内核代码(参见上面 内核CS段),其段基址为 0x00。因此其指向 0x7632 处。从 System.map 文件可以看到此处正是 system_call 函数。
Linux0.11 系统调用进程创建与执行(九)

2、 system_call 函数

其位于 kernel/system_call.s 中。
系统调用 0x80 会导致 CPU 硬件自动将 ssespeflagscseip 的值压栈。系统调用进入可参考 系统调用进入

# 错误的系统调用号
.align 2				# 内存 4 字节对齐
bad_sys_call:movl $-1,%eax		# eax 中置 -1,退出中断iret
# 重新执行调度程序入口。调度程序 schedule 在(kernel/sched.c,104)
# 当调度程序 schedule() 返回时就从 ret_from_sys_call 处(101行)继续执行	
.align 2
reschedule:pushl $ret_from_sys_call	# 将 ret_from_sys_call 的地址入栈jmp schedule
# int 0x80 --linux 系统调用入口点(调用中断 int 0x80,eax 中是调用号)。
.align 2
system_call:cmpl $nr_system_calls-1,%eax 	# 调用号如果超出范围的话就在 eax 中置-1 并退出。ja bad_sys_callpush %ds						# 保存原段寄存器值。push %espush %fs
# 一个系统调用最多可带有 3 个参数,也可以不带参数。下面入栈的 ebx、ecx 和 edx 中放着系统
# 调用相应 C 语言函数(见第 94 行)的调用参数。这几个寄存器入栈的顺序是由 GNU GCC 规定的,
# ebx 中可存放第 1 个参数,ecx 中存放第 2 个参数,edx 中存放第 3 个参数。
# 系统调用语句可参见头文件 include/unistd.h 中第 133 至 183 行的系统调用宏。pushl %edxpushl %ecx			# push %ebx, %ecx,%edx as parameterspushl %ebx			# to the system callmovl $0x10,%edx		# set up ds,es to kernel spacemov %dx,%ds			# ds,es 指向内核数据段(全局描述符表中数据段描述符)。mov %dx,%es
# fs 指向局部数据段(局部描述符表中数据段描述符),即指向执行本次系统调用的用户程序的数据段。
#注意,在 Linux 0.11 中内核给任务分配的代码和数据内存段是重叠的,它们的段基址和段限长相同。
# 参见 fork.c 程序中 copy_mem() 函数。mov1 $0x17, %edx	# fs points to local data spacemov %dx,%fs
# 下面这句操作数的含义是:调用地址=[_sys_call_table + %eax * 4]。参见程序后的说明。
# sys_call_table[]是一个指针数组,定义在 include/linux/sys.h 中。该指针数组中设置了
# 所有 72 个系统调用 C 处理函数的地址。call *sys_call_table(,%eax,4) 	# 间接调用指定功能 C 函数。
95:	pushl %eax						# 把系统调用返回值入栈。
# 下面 96-100 行查看当前任务的运行状态。如果不在就结状态(state 不等于 0)就去执行调度
#程序。如果该任务在就绪状态,但其时间片已用完(counter = 0),则也去执行调度程序。
#例如当后台进程组中的进程执行控制终端读写操作时,那么默认条件下该后台进程组所有进程。
# 会收到 SIGTTIN 或 SIGTTOU 信号,导致进程组中所有进程处于停止状态。而当前进程则会立刻。
# 返回。movl _current, %eax			#取当前任务(进程)数据结构地址到 eax。cmpl $0,state(%eax)			# statejne reschedulecmpl $0,counter(%eax)		# counterje reschedule
# 以下这段代码执行从系统调用 C 函数返回后,对信号进行识别处理。其他中断服务程序退出时也。
# 将跳转到这里进行处理后才退出中断过程,例如后面 131 行上的处理器出错中断 int 16。
101ret_from_sys_call:
# 首先判别当前任务是否是初始任务 task0,如果是则不必对其进行信号量方面的处理,直接返回。
# 103 行上的_task 对应 C 程序中的 task 数组,直接引用 task 相当于引用 task[0]。movl current,%eax		# task[0] cannot have signalscmpl task,%eaxje 3f					# 向前(forward)跳转到标号 3 处退出中断处理。
# 通过对原调用程序代码选择符的检查来判断调用程序是否是用户任务。如果不是则直接退出中断。
# 这是因为任务在内核态执行时不可抢占。否则对任务进行信号量的识别处理。这里比较选择符是否,
# 为用户代码段的选择符 0x000f(RPL=3,局部表,第 1 个段(代码段))来判断是否为用户任务。如
# 果不是则说明是某个中断服务程序跳转到第 101 行的,于是跳转退出中断程序。如果原堆栈段选择。
#符不为 0x17(即原堆栈不在用户段中),也说明本次系统调用的调用者不是用户任务,则也退出。cmpw $0x0f,CS(%esp)		# was old code segment supervisor ?jne 3fcmpw $0x17,OLDSS(%esp)		# was stack segment = 0x17 ?jne 3f
# 下面这段代码( 109-120 )用于处理当前任务中的信号。首先取当前任务结构中的信号位图(32 位,
# 每位代表 1 种信号),然后用任务结构中的信号阻塞(屏蔽)码,阻塞不允许的信号位,取得数值
# 最小的信号值,再把原信号位图中该信号对应的位复位(置 0),最后将该信号值作为参数之一调
# 用 do_signal()。 do_signal() 在(kernel/signal.c,82)中,其参数包括 13 个入栈的信息。movl signal(%eax),%ebx		#取信号位图放入ebx中, 每 1 位代表 1 种信号,共 32 个信号。movl blocked(%eax),%ecx		#取阻塞(屏蔽)信号位图放入ecx中。notl %ecx					# 每位取反。andl %ebx,%ecx				#获得许可的信号位图。bsfl %ecx,%ecx				# 从低位(位 0)开始扫描位图,看是否有 1 的位,# 若有,则 ecx 保留该位的偏移值(即第几位 0-31)。je 3f						# 如果没有信号则向前跳转退出。btrl %ecx,%ebx				# 复位该信号(ebx 含有原 signal 位图)。movl %ebx,signal(%eax)		# 重新保存 signal 位图信息到 current->signal 中。incl %ecx					# 将信号调整为从 1 开始的数(1-32)。pushl %ecx					# 信号值入栈作为调用 do_signal 的参数之一call do_signal				# 调用 C 函数信号处理程序(kernel/signal.c,82)popl %eax					# 弹出入栈的信号值。
3:	popl %eax					# eax 中含有第 95 行入栈的系统调用返回值。popl %ebxpopl %ecxpopl %edxpop %fspop %espop %dsiret

函数中 call *sys_call_table(,%eax,4) 实际调用的就是 sys_fork 函数。其也定义在当前文件中:

.align 2
sys_fork:call find_empty_processtestl %eax,%eax		# 在eax中返回进程号pid。若返回负数则退出js 1fpush %gspushl %esipushl %edipushl %ebppushl %eaxcall copy_processaddl $20,%esp		# 丢弃这里所有压栈内容,即上面压入的 gs、esi、edi、ebp、eax
1:	ret

   sys_fork 函数会调用 find_empty_process 函数找到一个空闲的任务,并返回进程号。然后调用 copy_process 拷贝父进程信息。

2.1 copy_process 函数

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,long ebx,long ecx,long edx,long fs,long es,long ds,long eip,long cs,long eflags,long esp,long ss)
{// ...
}

   GCC 中函数调用的参数逆次压入栈中(参考C 与汇编程序的相互调用 ,3.2 copy_process 函数 ),即最后压入变量 nrcopy_process 函数参数依次对应 %eax%ebp%edi%esi%gsnone(调用 sys_fork 函数时压入栈的返回地址),%ebx%ecx%edx%fs%es%dseipcseflagsespss

2.2 设置进程 1 的分页管理

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,long ebx,long ecx,long edx,long fs,long es,long ds,long eip,long cs,long eflags,long esp,long ss)
{// ...if (copy_mem(nr,p)) {		// 设置子进程的代码段、数据段及创建、复制子进程的第一个页表task[nr] = NULL;free_page((long) p);return -EAGAIN;}// ...
}

copy_men 函数在 kernel/fork.c 中。参考 3.2.1 copy_mem 函数

int copy_mem(int nr,struct task_struct * p)
{unsigned long old_data_base,new_data_base,data_limit;unsigned long old_code_base,new_code_base,code_limit;// 取子进程的代码、数据段限长,跟踪两者都为:655360code_limit=get_limit(0x0f);data_limit=get_limit(0x17);// 获取父进程(现在为进程 0)的代码段、数据段基址,// 跟踪两者都为 0old_code_base = get_base(current->ldt[1]);old_data_base = get_base(current->ldt[2]);if (old_data_base != old_code_base)panic("We don't support separate I&D");if (data_limit < code_limit)panic("Bad data_limit");new_data_base = new_code_base = nr * 0x4000000;p->start_code = new_code_base;set_base(p->ldt[1],new_code_base);	// 设置子进程代码段基址set_base(p->ldt[2],new_data_base);	// 设置子进程数据段基址if (copy_page_tables(old_data_base,new_data_base,data_limit)) {printk("free_page_tables: from copy_mem\\n");free_page_tables(new_data_base,data_limit);return -ENOMEM;}return 0;
}

copy_page_tables 函数请参考 3.2.1.1 copy_page_tables 函数

3、系统调用返回

sys_fork 返回后,其栈中数据如下:
%ebx%ecx%edx%fs%es%dseipcseflagsespss

# kernel/system_call.s95:	pushl %eax						# 把系统调用返回值入栈。
# 下面 96-100 行查看当前任务的运行状态。如果不在就结状态(state 不等于 0)就去执行调度
#程序。如果该任务在就绪状态,但其时间片已用完(counter = 0),则也去执行调度程序。
#例如当后台进程组中的进程执行控制终端读写操作时,那么默认条件下该后台进程组所有进程。
# 会收到 SIGTTIN 或 SIGTTOU 信号,导致进程组中所有进程处于停止状态。而当前进程则会立刻。
# 返回。movl _current, %eax			#取当前任务(进程)数据结构地址到 eax。cmpl $0,state(%eax)			# statejne reschedulecmpl $0,counter(%eax)		# counterje reschedule
# 以下这段代码执行从系统调用 C 函数返回后,对信号进行识别处理。其他中断服务程序退出时也。
# 将跳转到这里进行处理后才退出中断过程,例如后面 131 行上的处理器出错中断 int 16。
101ret_from_sys_call:
# 首先判别当前任务是否是初始任务 task0,如果是则不必对其进行信号量方面的处理,直接返回。
# 103 行上的_task 对应 C 程序中的 task 数组,直接引用 task 相当于引用 task[0]。movl current,%eax		# task[0] cannot have signalscmpl task,%eaxje 3f					# 向前(forward)跳转到标号 3 处退出中断处理。
# 通过对原调用程序代码选择符的检查来判断调用程序是否是用户任务。如果不是则直接退出中断。
# 这是因为任务在内核态执行时不可抢占。否则对任务进行信号量的识别处理。这里比较选择符是否,
# 为用户代码段的选择符 0x000f(RPL=3,局部表,第 1 个段(代码段))来判断是否为用户任务。如
# 果不是则说明是某个中断服务程序跳转到第 101 行的,于是跳转退出中断程序。如果原堆栈段选择。
#符不为 0x17(即原堆栈不在用户段中),也说明本次系统调用的调用者不是用户任务,则也退出。cmpw $0x0f,CS(%esp)		# was old code segment supervisor ?jne 3fcmpw $0x17,OLDSS(%esp)		# was stack segment = 0x17 ?jne 3f
# 下面这段代码( 109-120 )用于处理当前任务中的信号。首先取当前任务结构中的信号位图(32 位,
# 每位代表 1 种信号),然后用任务结构中的信号阻塞(屏蔽)码,阻塞不允许的信号位,取得数值
# 最小的信号值,再把原信号位图中该信号对应的位复位(置 0),最后将该信号值作为参数之一调
# 用 do_signal()。 do_signal() 在(kernel/signal.c,82)中,其参数包括 13 个入栈的信息。movl signal(%eax),%ebx		#取信号位图放入ebx中, 每 1 位代表 1 种信号,共 32 个信号。movl blocked(%eax),%ecx		#取阻塞(屏蔽)信号位图放入ecx中。notl %ecx					# 每位取反。andl %ebx,%ecx				#获得许可的信号位图。bsfl %ecx,%ecx				# 从低位(位 0)开始扫描位图,看是否有 1 的位,# 若有,则 ecx 保留该位的偏移值(即第几位 0-31)。je 3f						# 如果没有信号则向前跳转退出。btrl %ecx,%ebx				# 复位该信号(ebx 含有原 signal 位图)。movl %ebx,signal(%eax)		# 重新保存 signal 位图信息到 current->signal 中。incl %ecx					# 将信号调整为从 1 开始的数(1-32)。pushl %ecx					# 信号值入栈作为调用 do_signal 的参数之一call do_signal				# 调用 C 函数信号处理程序(kernel/signal.c,82)popl %eax					# 弹出入栈的信号值。
3:	popl %eax					# eax 中含有第 95 行入栈的系统调用返回值。popl %ebxpopl %ecxpopl %edxpop %fspop %espop %dsiret

4、寄存器信息

4.1 进程 0

其 ldt ,tss 不变。

4.1.1 内核态信息

Linux0.11 系统调用进程创建与执行(九)

4.1.2 用户态信息

Linux0.11 系统调用进程创建与执行(九)
   从用户态切换到内核态,处理器从当前执行任务的 TSS 段中得到异常处理过程(内核态)使用的堆栈的段选择符和栈指针(例如 tss.ss0tss.esp0)。然后处理器会把被中断程序(或任务)的 栈选择符栈指针 压入新栈中。接着处理器会把 EFLAGSCSEIP 寄存器的当前值也压入新栈中。
   结合上面可知,其 csssdses 发生了改变。除了系统调用时处理器会自动改变 sses 的值。其它值是在 kernel/system_call.s 文件中 system_call 函数中改变的,其改变了dses,以及 fs 的值 。

	movl $0x10,%edx		# set up ds,es to kernel spacemov %dx,%ds			# ds,es 指向内核数据段(全局描述符表中数据段描述符)。mov %dx,%es
# fs 指向局部数据段(局部描述符表中数据段描述符),即指向执行本次系统调用的用户程序的数据段。
#注意,在 Linux 0.11 中内核给任务分配的代码和数据内存段是重叠的,它们的段基址和段限长相同。
# 参见 fork.c 程序中 copy_mem() 函数。mov1 $0x17, %edx	# fs points to local data spacemov %dx,%fs

系统调用进入可参考 系统调用进入,系统调用返回可参考 系统调用返回。

4.1.3 task_struct 中其它字段

Linux0.11 系统调用进程创建与执行(九)

4.2 进程 1

4.2.1 初始化时信息:

ldt

Linux0.11 系统调用进程创建与执行(九)

tss

Linux0.11 系统调用进程创建与执行(九)

4.2.2 用户态信息

通用寄存器和段寄存器

Linux0.11 系统调用进程创建与执行(九)

ldttss 不变。

4.2.3 内核态信息

通用寄存器和段寄存器

Linux0.11 系统调用进程创建与执行(九)

ldttss 不变。

4.2.4 task_struct 中其它字段

Linux0.11 系统调用进程创建与执行(九)

四、进程 2

1、调用 fork 创建进程 2

void init(void) {// ...if (!(pid = fork())) {close(0);if (open("/etc/rc", O_RDONLY, 0))_exit(1);execve("/bin/sh", argv_rc, envp_rc);_exit(2);}// ...  

通用寄存器和段寄存器

Linux0.11 系统调用进程创建与执行(九)

ldt

Linux0.11 系统调用进程创建与执行(九)

tss

Linux0.11 系统调用进程创建与执行(九)

task_struct 中其它字段

Linux0.11 系统调用进程创建与执行(九)
除了 start_code 其它字段与进程 0 和 进程 1 类似。其中的 ldttss 可以参看上图。

2、进程 2 执行 execve 函数

execve 是个系统调用,最终会调用 fs/exec.c 文件中的 do_execve 函数。此时程序处在进程 2 的内核态。其通用寄存器和段寄存器未发生改变。详细可以参考:Linux0.11 execve函数(六)

ldt

Linux0.11 系统调用进程创建与执行(九)
ldt[2] 值为:0x08c0f300:3fff,此 LDT 描述符图释如下:详细可参考:4、段描述符
Linux0.11 系统调用进程创建与执行(九)
由上图可知:其段基址为 128MB,处在用户态,段限长为 64MB

tss

Linux0.11 系统调用进程创建与执行(九)

task_struct 中其它字段

Linux0.11 系统调用进程创建与执行(九)
进程 2 相比进程 01,其现在 end_codeend_databrkexcutable 都有值了。这些值都是在 do_execve 函数中赋予的。

中断

task0

Linux0.11 系统调用进程创建与执行(九)
信息被压入到 tss 中。

task1

Linux0.11 系统调用进程创建与执行(九)