进程调度

认证

学号末尾三位:474
本实验来源于—— https://github.com/mengning/linuxkernel/

实验要求

  • 从整理上理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和进程切换
  • 阅读理解task_struct数据结构
    http://codelab.shiyanlou.com/xref/linux3.18.6/include/linux/sched.h#1235;
  • 分析fork函数对应的内核处理过程do_fork,理解创建一个新进程如何创建和修改task_struct数据结构;
  • 使用gdb跟踪分析一个fork系统调用内核处理函数do_fork ,验证您对Linux系统创建一个新进程的理解,特别关注新进程是从哪里开始执行的?为什么从那里能顺利执行下去?即执行起点与内核堆栈如何保证一致。
  • 理解编译链接的过程和ELF可执行文件格式
  • 编程使用exec*库函数加载一个可执行文件,动态链接分为可执行程序装载时动态链接和运行时动态链接;
  • 使用gdb跟踪分析一个execve系统调用内核处理函数do_execve ,验证您对Linux系统加载可执行程序所需处理过程的理解;
  • 特别关注新的可执行程序是从哪里开始执行的?为什么execve系统调用返回后新的可执行程序能顺利执行?对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同?
  • 理解Linux系统中进程调度的时机,可以在内核代码中搜索schedule()函数,看都是哪里调用了schedule(),判断我们课程内容中的总结是否准确;
  • 使用gdb跟踪分析一个schedule()函数 ,验证您对Linux系统进程调度与进程切换过程的理解;
    特别关注并仔细分析switch_to中的汇编代码,理解进程上下文的切换机制,以及与中断上下文切换的关系;

实验内容

实验操作

创建进程并跟踪分析

  • 写一个简单的使用fork系统调用的程序集成到menuOS中
int ForkProcess()
{
	int pid;
	/* fork another process */
	pid = fork();
	if (pid<0)
	{
	/* error occurred */
	printf("Fork Failed!");
	//exit(-1);
	}
	else if (pid==0)
	{
	/*  child process */
	//execlp("/bin/ls","ls",NULL);
	printf("This is child Process!,my PID is %d\n",pid);
	}
	else
	{
	/* parent process  */
	/* parent will wait for the child to complete*/
	printf("THis is parent process!,my process is %d\n",pid);	
	//wait(NULL);
	printf("Child Complete!");
	//exit(0);
	}
	return 0;
}
  • 调用fork()创建一个子进程,以下是这个函数在qemu中的运行结果
    在这里插入图片描述
  • 分别在_do_fork,copy_process,dup_task_struct处打断点
    在这里插入图片描述
  • 跟踪发现_do_fork()进入copy_process()复制父进程信息获取pid,进入copy_process()查看具体代码。
    在这里插入图片描述
小结1

进程描述:内核中用进程描述符来表示进程,他提供了进程相关的所有信息,例如状态、进程双向链表管理、控制台、文件系统、内存管理、进程间通信等等。

/* 进程描述符 */
struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	void *stack;
	atomic_t usage;
	unsigned int flags;	/* per process flags, defined below */
	unsigned int ptrace;
         ...
}

进程创建总结:

  • fork函数创建进程的实质是子进程对父进程的复制,fork()函数调用一次会返回两次,在父进程中返回的是创建的子进程的pid,在子进程中返回的是0,因此可以通过返回的pid的值来判断输出的信息是来自于子进程还是父进程。
  • _do_fork()主要通过调用copy_process()复制父进程信息获取pid,pid是copy_process的返回值,并调用wake_up_new_task将子进程加入调度队列。
  • copy_process内部调用dup_task_struct复制父进程的进程描述符,调用copy_thread初始化子进程内核栈,采用写时复制技术复制其他进程资源,设置子进程pid,把进程状态设置为TASK_RUNNING等。

可执行程序工作原理

ELF可执行文件格式

ELF文件格式包括三种主要的类型:可执行文件、可重定向文件、共享库。

可重定位文件:一般是中间文件,需要和其他文件一起来创建可执行文件、静态库文件、共享目标文件。
可执行文件:文件中保存着一个用来执行的文件。
共享目标文件:指可以被可执行文件或其他库文件使用的目标。

编译链接过程
在这里插入图片描述

编程使用exec*库函数加载一个可执行文件:
编辑文件myexec.c,生成预处理文件myexec.cpp,编译成汇编代码myexec.s,编译成目标代码,即二进制文件myexec.o,链接成可执行文件myexec,运行./myexec。
execve函数过程描述

execve函数过程描述
整体调用关系为 execve->sys_execve->do_execve() -> do_execve_common()->exec_binprm()->search_binary_handler()->load_elf_binary()->start_thread()。


大致处理过程如下:

  • sys_execve中的do_execve() 读取128个字节的文件头部,判断可执行文件的类型
  • 调用search_binary_handler()搜索和匹配合适的可执行文件装载处理过程。
  • ELF文件由load_elf_binary()函数负责装载,load_elf_binary()函数调用了start_thread函数,创建新进程的堆栈

gdb跟踪

  • Linux提供了execl、execlp、execle、execv、execvp和execve等6个用以执行一个可执行文件的函数。这些函数的本质都是调用sys_execve()来执行一个可执行文件。使用gdb跟踪do_execve。
    在这里插入图片描述
    在这里插入图片描述

进程调度

进程调度的时机:

  • 中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();
  • 内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度;
  • 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。

schedule运行机制:

  • schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换。context_switch首先调用switch_mm切换CR3,然后调用宏switch_to来进行硬件上的上下文切换。
  • gdb跟踪schedule
    在这里插入图片描述
  • 分析switch_to中的汇编代码,理解进程上下文的切换机制,以及与中断上下文切换的关系
/* 
首先在当前进程prev的内核栈中保存esi,edi及ebp寄存器的内容。然后将prev的内核堆栈指针esp
存入prev->thread.esp中。把将next进程的内核栈指针next->thread.esp置入esp寄存器中,将当
前进程的地址保存在prev-thread.eip中,这个地址就是prev下一次被调度,通过jmp指令转入一个函
数__switch_to,__switch_to中jmp与return的匹配,return 会弹出返回地址,因为jmp不会压栈
,return弹出的则是>栈顶地址即$1f标识之处。恢复next上次被调离时推进堆栈的内容。next进程开始
执行。
*/
#define switch_to(prev, next, last)					\
32do {									\
33	/*								\
34	 * Context-switching clobbers all registers, so we clobber	\
35	 * them explicitly, via unused output variables.		\
36	 * (EAX and EBP is not listed because EBP is saved/restored	\
37	 * explicitly for wchan access and EAX is the return value of	\
38	 * __switch_to())						\
39	 */								\
40	unsigned long ebx, ecx, edx, esi, edi;				\
41									\
42	asm volatile("pushfl\n\t"		/* 保存当前进程flags */	
43		     "pushl %%ebp\n\t"		/* 保存当前进程的堆栈基址EBP    */	
44		     "movl %%esp,%[prev_sp]\n\t"	/* 保存当前栈顶ESP */ 
45		     "movl %[next_sp],%%esp\n\t"	/* 将下一栈栈顶保存到esp中  */ 
//完成内核堆栈的切换
46		     "movl $1f,%[prev_ip]\n\t"	/* 保存当前进程的EIP    */	
47		     "pushl %[next_ip]\n\t"	/* 把next进程的起点EIP压入堆栈   */	
48		     __switch_canary		
 		//next_ip一般是$if,对于新创建的子进程是ret_from_fork
49		     "jmp __switch_to\n"	/* prev进程中,设置next进程堆栈  */	

50		     "1:\t"		       //next进程开始执行		
51		     "popl %%ebp\n\t"		/* restore EBP   */	\
52		     "popfl\n"			/* restore flags */	\
53									\
54		     /* output parameters */				\
55		     : [prev_sp] "=m" (prev->thread.sp),		\
56		       [prev_ip] "=m" (prev->thread.ip),		\
57		       "=a" (last),					\
58									\
59		       /* clobbered output registers: */		\
60		       "=b" (ebx), "=c" (ecx), "=d" (edx),		\
61		       "=S" (esi), "=D" (edi)				\
62		       							\
63		       __switch_canary_oparam				\
64									\
65		       /* input parameters: */				\
66		     : [next_sp]  "m" (next->thread.sp),		\
67		       [next_ip]  "m" (next->thread.ip),		\
68		       							\
69		       /* regparm parameters for __switch_to(): */	\
70		       [prev]     "a" (prev),				\
71		       [next]     "d" (next)				\
72									\
73		       __switch_canary_iparam				\
74									\
75		     : /* reloaded segment registers */			\
76			"memory");					\
77} while (0)
小结2

进程上下文切换需要保存切换进程的相关信息(thread.sp和thread.ip);中断上下文的切换是在一个进程的用户态到一个进程的内核态,或从进程的内核态到用户态,切换进程需要在不同的进程间切换,但一般进程上下文切换是套在中断上下文切换中的。例如,系统调用作为中断陷入内核,,调用schedule函数发生进程上下文切换,系统调用返回,完成中断上下文的切换。

实验总结

linux内核调度执行过程

  • 运行用户态进程U
  • 发生中断:save cs:eip/ss:eip/eflags,加载当前进程内核堆栈,跳转至中断处理程序
  • SAVE_ALL,保存现场,完成中断上下文的切换。
  • 中断处理过程若调用了schedule函数,其中switch_to做进程上下文的切换。(假设由进程U到进程M)
  • $1f之后,运行用户态进程M
  • restore_all,恢复现场
  • iret 从U进程内核堆栈弹出硬件完成的压栈内容,完成中断上下文的切换,即U的内核态到用户态。
  • 继续运行U。

注:

  • 看到该博主清晰的总结,顾引用之https://blog.csdn.net/qq_34080360/article/details/88746948
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值