分析进程创建、执行、切换以及可执行文件的加载

sa18225499 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/

一、实验步骤及分析

进程描述

我们通过进程控制块来描述来描述进程,又称其为进程描述符,他提供了进程相关的所有信息,例如状态、进程双向链表管理、控制台、文件系统、内存管理、进程间通信等等。

  1. struct task_struct {
  2. volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
  3. void *stack;
  4. atomic_t usage;
  5. unsigned int flags; /* per process flags, defined below */
  6. unsigned int ptrace;
  7. ...
  8. }

进程状态转换图

分析进程创建、执行、切换以及可执行文件的加载

使用fork进程创建子进程后,新进程的状态是TASK_RUNNING,将此进程从就绪队列调度执行时状态也是TASK_RUNNING。

进程创建

0号进程初始化是通过硬编码固定下来的,init_task为0号进程的进程描述符的结构体变量。init_task初始化如下:

  1. struct task_struct init_task = INIT_TASK(init_task);
  2. EXPORT_SYMBOL(init_task);

 rest_init调用kernel_thread创建1号进程和2号进程,一个是kernel_init,init用户进程;另一个是kthreadd进程,是所有内核进程的祖先,管理内核进程。

  1. static noinline void __init_refok rest_init(void){
  2. ....
  3. kernel_thread(kernel_init,NULL,CLONE_FILES);
  4. ....
  5. pid = kernel_thread(kthreadd,NULL,CLONE_FS|CLONE_FILES);
  6. ....
  7. }

使用fork函数创建进程

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<unistd.h>
  4. int main()
  5. {
  6. int pid;
  7. pid = fork();
  8. if(pid < 0)
  9. {
  10. fprintf("stderr","failed!");
  11. }
  12. else if(pid == 0)
  13. {
  14. printf("This is child process!\n");
  15. }
  16. else
  17. {
  18. printf("This is parent process!\n");
  19. wait(NULL);
  20. printf("child complete!\n");
  21. }
  22. return 0;
  23. }

 fork系统调用把当前进程又复制了一个子进程,此时两个进程执行相同的代码,只是父进程和子进程的返回值不同,而两个进程输出信息在一个终端。

分析进程创建、执行、切换以及可执行文件的加载

fork系统调用

fork系统调用时,用户态用int $0x80指令触发中断机制,cpu自动从用户态堆栈转为内核态堆栈,将ss:esp,cs:eip以及eflags压到当前进程内核堆栈中,接下来执行system_call,其用于保存现场,调用系统调用内核函数,处理完后返回,恢复现场。最后iret将ss:esp,cs:eip以及eflags从内核堆栈恢复到相应寄存器中。

fork,vfork以及clone 、kernel_thread函数都是通过do_fork来创建一个进程,参考代码如下

  1. 1696pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
  2. 1697{
  3. 1698 return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
  4. 1699 (unsigned long)arg, NULL, NULL);
  5. 1700}
  6. 1701
  7. 1702#ifdef __ARCH_WANT_SYS_FORK
  8. 1703SYSCALL_DEFINE0(fork)
  9. 1704{
  10. 1705#ifdef CONFIG_MMU
  11. 1706 return do_fork(SIGCHLD, 0, 0, NULL, NULL);
  12. 1707#else
  13. 1708 /* can not support in nommu mode */
  14. 1709 return -EINVAL;
  15. 1710#endif
  16. 1711}
  17. 1712#endif
  18. 1713
  19. 1714#ifdef __ARCH_WANT_SYS_VFORK
  20. 1715SYSCALL_DEFINE0(vfork)
  21. 1716{
  22. 1717 return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
  23. 1718 0, NULL, NULL);
  24. 1719}
  25. 1720#endif
  26. 1721
  27. 1722#ifdef __ARCH_WANT_SYS_CLONE
  28. 1723#ifdef CONFIG_CLONE_BACKWARDS
  29. 1724SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
  30. 1725 int __user *, parent_tidptr,
  31. 1726 int, tls_val,
  32. 1727 int __user *, child_tidptr)
  33. 1728#elif defined(CONFIG_CLONE_BACKWARDS2)
  34. 1729SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
  35. 1730 int __user *, parent_tidptr,
  36. 1731 int __user *, child_tidptr,
  37. 1732 int, tls_val)
  38. 1733#elif defined(CONFIG_CLONE_BACKWARDS3)
  39. 1734SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
  40. 1735 int, stack_size,
  41. 1736 int __user *, parent_tidptr,
  42. 1737 int __user *, child_tidptr,
  43. 1738 int, tls_val)
  44. 1739#else
  45. 1740SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
  46. 1741 int __user *, parent_tidptr,
  47. 1742 int __user *, child_tidptr,
  48. 1743 int, tls_val)
  49. 1744#endif
  50. 1745{
  51. 1746 return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
  52. 1747}
  53. 1748#endif
  54. 1749

do_fork 函数代码如下,便于理解为其添加注释。

  1. long do_fork(unsigned long clone_flags,
  2. 1624 unsigned long stack_start,
  3. 1625 unsigned long stack_size,
  4. 1626 int __user *parent_tidptr,
  5. 1627 int __user *child_tidptr)
  6. 1628{
  7. 1629 struct task_struct *p;//进程描述符指针
  8. 1630 int trace = 0;
  9. 1631 long nr;//子进程id
  10. 1632
  11. 1633 /*
  12. 1634 * Determine whether and which event to report to ptracer. When
  13. 1635 * called from kernel_thread or CLONE_UNTRACED is explicitly
  14. 1636 * requested, no event is reported; otherwise, report if the event
  15. 1637 * for the type of forking is enabled.
  16. 1638 */
  17. 1639 if (!(clone_flags & CLONE_UNTRACED)) {
  18. 1640 if (clone_flags & CLONE_VFORK)
  19. 1641 trace = PTRACE_EVENT_VFORK;
  20. 1642 else if ((clone_flags & CSIGNAL) != SIGCHLD)
  21. 1643 trace = PTRACE_EVENT_CLONE;
  22. 1644 else
  23. 1645 trace = PTRACE_EVENT_FORK;
  24. 1646
  25. 1647 if (likely(!ptrace_event_enabled(current, trace)))
  26. 1648 trace = 0;
  27. 1649 }
  28. 1650
  29. 1651 p = copy_process(clone_flags, stack_start, stack_size,
  30. 1652 child_tidptr, NULL, trace);//创建子进程描述符
  31. 1653 /*
  32. 1654 * Do this prior waking up the new thread - the thread pointer
  33. 1655 * might get invalid after that point, if the thread exits quickly.
  34. 1656 */
  35. 1657 if (!IS_ERR(p)) {//copy_prpcess执行成功
  36. 1658 struct completion vfork;
  37. 1659 struct pid *pid;
  38. 1660
  39. 1661 trace_sched_process_fork(current, p);
  40. 1662
  41. 1663 pid = get_task_pid(p, PIDTYPE_PID);//获得task结构体的pid
  42. 1664 nr = pid_vnr(pid);//根据pid结构体获得进程pid
  43. 1665
  44. 1666 if (clone_flags & CLONE_PARENT_SETTID)
  45. 1667 put_user(nr, parent_tidptr);
  46. 1668
  47. 1669 if (clone_flags & CLONE_VFORK) {
  48. 1670 p->vfork_done = &vfork;
  49. 1671 init_completion(&vfork);
  50. 1672 get_task_struct(p);
  51. 1673 }
  52. 1674
  53. 1675 wake_up_new_task(p);//将子进程添加到调度器的队列
  54. 1676
  55. 1677 /* forking complete and child started to run, tell ptracer */
  56. 1678 if (unlikely(trace))
  57. 1679 ptrace_event_pid(trace, pid);
  58. 1680
  59. 1681 if (clone_flags & CLONE_VFORK) {
  60. 1682 if (!wait_for_vfork_done(p, &vfork))
  61. 1683 ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
  62. 1684 }
  63. 1685
  64. 1686 put_pid(pid);
  65. 1687 } else {
  66. 1688 nr = PTR_ERR(p);//异常处理
  67. 1689 }
  68. 1690 return nr;//返回子进程pid
  69. 1691}

do_fork函数主要完成了以下工作:

  • 通过调用copy_process()复制父进程信息,创建描述符及其他的数据结构
  • 获得pid
  • 调用wake_up_new_task(p)将子进程加入调度器队列,等待cpu资源运行

copy_process函数

  1. static struct task_struct *copy_process(unsigned long clone_flags,
  2. 1183 unsigned long stack_start,
  3. 1184 unsigned long stack_size,
  4. 1185 int __user *child_tidptr,
  5. 1186 struct pid *pid,
  6. 1187 int trace)
  7. 1188{
  8. 1189 int retval;
  9. 1190 struct task_struct *p;
  10. ...
  11. 1235 retval = security_task_create(clone_flags);//安全性检查
  12. ...
  13. 1240 p = dup_task_struct(current);//核心语句,复制pcb,内核栈
  14. ...
  15. 1252 retval = -EAGAIN;
  16. //检查用户进程数是否超过限制
  17. 1253 if (atomic_read(&p->real_cred->user->processes) >=
  18. 1254 task_rlimit(p, RLIMIT_NPROC)) {
  19. 1255 if (p->real_cred->user != INIT_USER &&
  20. 1256 !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
  21. 1257 goto bad_fork_free;
  22. 1258 }
  23. ...
  24. //检查进程数量是否超过max_threads
  25. 1271 if (nr_threads >= max_threads)
  26. 1272 goto bad_fork_cleanup_count;
  27. 1273
  28. 1274 if (!try_module_get(task_thread_info(p)->exec_domain->module))
  29. 1275 goto bad_fork_cleanup_count;
  30. 1276
  31. ...
  32. 1360 retval = sched_fork(clone_flags, p);//初始化新进程调度数据结构,将新进程状态设置为TASK_RUNNING
  33. 1361 if (retval)
  34. 1362 goto bad_fork_cleanup_policy;
  35. 1363
  36. 1364 retval = perf_event_init_task(p);
  37. 1365 if (retval)
  38. 1366 goto bad_fork_cleanup_policy;
  39. 1367 retval = audit_alloc(p);
  40. 1368 if (retval)
  41. 1369 goto bad_fork_cleanup_perf;
  42. 1370 /* copy all the process information */
  43. 1371 shm_init_task(p);
  44. 1372 retval = copy_semundo(clone_flags, p);
  45. 1373 if (retval)
  46. 1374 goto bad_fork_cleanup_audit;
  47. 1375 retval = copy_files(clone_flags, p);
  48. 1376 if (retval)
  49. 1377 goto bad_fork_cleanup_semundo;
  50. 1378 retval = copy_fs(clone_flags, p);
  51. 1379 if (retval)
  52. 1380 goto bad_fork_cleanup_files;
  53. 1381 retval = copy_sighand(clone_flags, p);
  54. 1382 if (retval)
  55. 1383 goto bad_fork_cleanup_fs;
  56. 1384 retval = copy_signal(clone_flags, p);
  57. 1385 if (retval)
  58. 1386 goto bad_fork_cleanup_sighand;
  59. 1387 retval = copy_mm(clone_flags, p);
  60. 1388 if (retval)
  61. 1389 goto bad_fork_cleanup_signal;
  62. 1390 retval = copy_namespaces(clone_flags, p);
  63. 1391 if (retval)
  64. 1392 goto bad_fork_cleanup_mm;
  65. 1393 retval = copy_io(clone_flags, p);
  66. 1394 if (retval)
  67. 1395 goto bad_fork_cleanup_namespaces;
  68. 1396 retval = copy_thread(clone_flags, stack_start, stack_size, p);//初始化子进程内核栈
  69. ...
  70. 1548
  71. 1549 return p;//返回被创建的子进程描述符指针p
  72. ...
  73. }

copy_process函数主要完成以下工作:

  • 调用dup_task_struct复制父进程描述符 
  • 调用copy_thread初始化子进程内核栈
  • 将子进程置为就绪态
  • 采用写时复制技术复制其他进程资源
  • 设置子进程pid

dup_task_struct函数

 

  1. 305static struct task_struct *dup_task_struct(struct task_struct *orig)
  2. 306{
  3. 307 struct task_struct *tsk;
  4. 308 struct thread_info *ti;
  5. 309 int node = tsk_fork_get_node(orig);
  6. 310 int err;
  7. 311
  8. 312 tsk = alloc_task_struct_node(node);//为子进程创建进程描述符分配存储空间
  9. 313 if (!tsk)
  10. 314 return NULL;
  11. 315
  12. 316 ti = alloc_thread_info_node(tsk, node);//创建两个页,一部分存放thread_info,另一部分存放内核堆栈
  13. 317 if (!ti)
  14. 318 goto free_tsk;
  15. 319
  16. 320 err = arch_dup_task_struct(tsk, orig);//复制父进程task_struct
  17. 321 if (err)
  18. 322 goto free_ti;
  19. 323
  20. 324 tsk->stack = ti;//将栈底的值赋给新节点的stack
  21. 325#ifdef CONFIG_SECCOMP
  22. 326 /*
  23. 327 * We must handle setting up seccomp filters once we're under
  24. 328 * the sighand lock in case orig has changed between now and
  25. 329 * then. Until then, filter must be NULL to avoid messing up
  26. 330 * the usage counts on the error path calling free_task.
  27. 331 */
  28. 332 tsk->seccomp.filter = NULL;
  29. 333#endif
  30. 334//初始化子进程的thread_info
  31. 335 setup_thread_stack(tsk, orig);
  32. 336 clear_user_return_notifier(tsk);
  33. 337 clear_tsk_need_resched(tsk);
  34. 338 set_task_stack_end_magic(tsk);
  35. 339
  36. 340#ifdef CONFIG_CC_STACKPROTECTOR
  37. 341 tsk->stack_canary = get_random_int();
  38. 342#endif
  39. 343
  40. 344 /*
  41. 345 * One for us, one for whoever does the "release_task()" (usually
  42. 346 * parent)
  43. 347 */
  44. 348 atomic_set(&tsk->usage, 2);
  45. 349#ifdef CONFIG_BLK_DEV_IO_TRACE
  46. 350 tsk->btrace_seq = 0;
  47. 351#endif
  48. 352 tsk->splice_pipe = NULL;
  49. 353 tsk->task_frag.page = NULL;
  50. 354
  51. 355 account_kernel_stack(ti, 1);
  52. 356
  53. 357 return tsk;
  54. 358
  55. 359free_ti:
  56. 360 free_thread_info(ti);
  57. 361free_tsk:
  58. 362 free_task_struct(tsk);
  59. 363 return NULL;
  60. 364}

dup_task_struct函数创建两个页,一部分存放thread_info,另一部分存放内核堆栈,复制父进程task_struct ,thread_info结构,然后将task指针指向子进程的进程描述符。

分析进程创建、执行、切换以及可执行文件的加载

copy_thread函数

  1. int copy_thread(unsigned long clone_flags, unsigned long sp,
  2. 133 unsigned long arg, struct task_struct *p)
  3. 134{
  4. 135 struct pt_regs *childregs = task_pt_regs(p);
  5. 136 struct task_struct *tsk;
  6. 137 int err;
  7. 138
  8. 139 p->thread.sp = (unsigned long) childregs;
  9. 140 p->thread.sp0 = (unsigned long) (childregs+1);
  10. 141 memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
  11. 142
  12. 143 if (unlikely(p->flags & PF_KTHREAD)) {//内核线程
  13. 144 /* kernel thread */
  14. 145 memset(childregs, 0, sizeof(struct pt_regs));
  15. //内核进程,从ret_from_kernel_thread开始执行
  16. 146 p->thread.ip = (unsigned long) ret_from_kernel_thread;
  17. 147 task_user_gs(p) = __KERNEL_STACK_CANARY;
  18. 148 childregs->ds = __USER_DS;
  19. 149 childregs->es = __USER_DS;
  20. 150 childregs->fs = __KERNEL_PERCPU;
  21. 151 childregs->bx = sp; /* function */
  22. 152 childregs->bp = arg;
  23. 153 childregs->orig_ax = -1;
  24. 154 childregs->cs = __KERNEL_CS | get_kernel_rpl();
  25. 155 childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
  26. 156 p->thread.io_bitmap_ptr = NULL;
  27. 157 return 0;
  28. 158 }
  29. //复制内核堆栈(复制父进程寄存器信息)
  30. 159 *childregs = *current_pt_regs();
  31. 160 childregs->ax = 0;//子进程eax =0,返回值为0
  32. 161 if (sp)
  33. 162 childregs->sp = sp;
  34. 163//从ret_from_fork执行
  35. 164 p->thread.ip = (unsigned long) ret_from_fork;
  36. 165 task_user_gs(p) = get_user_gs(current_pt_regs());
  37. 166
  38. 167 p->thread.io_bitmap_ptr = NULL;
  39. 168 tsk = current;
  40. 169 err = -ENOMEM;
  41. 170
  42. 171 if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {
  43. 172 p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,
  44. 173 IO_BITMAP_BYTES, GFP_KERNEL);
  45. 174 if (!p->thread.io_bitmap_ptr) {
  46. 175 p->thread.io_bitmap_max = 0;
  47. 176 return -ENOMEM;
  48. 177 }
  49. 178 set_tsk_thread_flag(p, TIF_IO_BITMAP);
  50. 179 }
  51. 180
  52. 181 err = 0;
  53. 182
  54. 183 /*
  55. 184 * Set a new TLS for the child thread?
  56. 185 */
  57. 186 if (clone_flags & CLONE_SETTLS)
  58. 187 err = do_set_thread_area(p, -1,
  59. 188 (struct user_desc __user *)childregs->si, 0);
  60. 189
  61. 190 if (err && p->thread.io_bitmap_ptr) {
  62. 191 kfree(p->thread.io_bitmap_ptr);
  63. 192 p->thread.io_bitmap_max = 0;
  64. 193 }
  65. 194 return err;
  66. 195}
  67. 196

 copy_thread函数是对内核信息的初始化。子进程开始执行的起点分为两种情况,内核进程从ret_from_kernel_thread开始执行,用户态进程从ret_from_fork执行。

使用gdb调试fork

使用以下指令启动menuOS

  1. cd LinuxKernel
  2. rm menu -rf
  3. git clone https://github.com/mengning/menu.git
  4. cd menu
  5. mv test_fork.c test.c
  6. make rootfs

分析进程创建、执行、切换以及可执行文件的加载

打开另一个shell窗口,将当前目录设为LinuxKernel,进行gdb调试

  1. cd LinuxKernel
  2. gdb
  3. file linux-3.18.6/vmlinux
  4. target remote:1234

分析进程创建、执行、切换以及可执行文件的加载

 

分别在sys_clone、do_fork、dup_task_struct、copy_process、copy_thread、ret_from_fork出设置断点。

分析进程创建、执行、切换以及可执行文件的加载

停在断点 sys_clone处。

分析进程创建、执行、切换以及可执行文件的加载

停在断点do_fork处

分析进程创建、执行、切换以及可执行文件的加载

停在断点dup_task_struct处

分析进程创建、执行、切换以及可执行文件的加载

停在断点copy_process处 

分析进程创建、执行、切换以及可执行文件的加载

停在断点copy_thread处

分析进程创建、执行、切换以及可执行文件的加载

编程使用exec*库函数加载一个可执行文件

编辑文件myexec.c,生成预处理文件myexec.cpp,编译成汇编代码myexec.s,编译成目标代码,即二进制文件myexec.o,链接成可执行文件myexec,运行./myexec。

分析进程创建、执行、切换以及可执行文件的加载

分析进程创建、执行、切换以及可执行文件的加载

编译链接的过程和ELF可执行文件格式 

编译链接过程

分析进程创建、执行、切换以及可执行文件的加载

ELF可执行文件格式

可重定位文件:一般是中间文件,需要和其他文件一起来创建可执行文件、静态库文件、共享目标文件。

可执行文件:文件中保存着一个用来执行的文件。

共享目标文件:指可以被可执行文件或其他库文件使用的目标。

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跟踪分析一个execve系统调用内核处理函数do_execve

分别在sys_execve、do_execve() 、do_execve_common()、exec_binprm()、search_binary_handler()、load_elf_binary()、start_thread()设置断点

分析进程创建、执行、切换以及可执行文件的加载

断点 do _execve处,可以看出其调用do_execve_common()函数。分析进程创建、执行、切换以及可执行文件的加载

新的可执行程序执行起点以及为什么execve系统调用返回后新的可执行程序能顺利执行?

新的可执行程序开始执行的起点在于修改的调用 execve系统调用时压入内核堆栈的EIP的值,此时标志着当前进程可执行文件已经完全替换成新的可执行文件。

对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同?

静态链接elf_entry指向可执行文件的头部,一般是main函数,是新程序执行的起点,一般地址为0x8048XXX的位置。

动态链接:elf_entry指向ld即动态链接器的起点load_elf_interp。

使用gdb调试一个schedule()函数

分别在schedule,pick_next_task,context_switch,__switch_to设置断点

分析进程创建、执行、切换以及可执行文件的加载

schedule调用_schedule,_schedule调用pick_next_task,context_switch函数,context_switch函数调用__switch_to。pick_next_task函数是根据调度策略和调度算法选择下一进程,context_switch函数负责进程的切换。

分析switch_to中的汇编代码,理解进程上下文的切换机制,以及与中断上下文切换的关系

  1. #define switch_to(prev, next, last) \
  2. 32do { \
  3. 33 /* \
  4. 34 * Context-switching clobbers all registers, so we clobber \
  5. 35 * them explicitly, via unused output variables. \
  6. 36 * (EAX and EBP is not listed because EBP is saved/restored \
  7. 37 * explicitly for wchan access and EAX is the return value of \
  8. 38 * __switch_to()) \
  9. 39 */ \
  10. 40 unsigned long ebx, ecx, edx, esi, edi; \
  11. 41 \
  12. 42 asm volatile("pushfl\n\t" /* 保存当前进程flags */
  13. 43 "pushl %%ebp\n\t" /* 保存当前进程的堆栈基址EBP */
  14. 44 "movl %%esp,%[prev_sp]\n\t" /* 保存当前栈顶ESP */
  15. 45 "movl %[next_sp],%%esp\n\t" /* 将下一栈栈顶保存到esp中 */
  16. //完成内核堆栈的切换
  17. 46 "movl $1f,%[prev_ip]\n\t" /* 保存当前进程的EIP */
  18. 47 "pushl %[next_ip]\n\t" /* 把next进程的起点EIP压入堆栈 */
  19. 48 __switch_canary
  20. //next_ip一般是$if,对于新创建的子进程是ret_from_fork
  21. 49 "jmp __switch_to\n" /* prev进程中,设置next进程堆栈 */
  22. 50 "1:\t" //next进程开始执行
  23. 51 "popl %%ebp\n\t" /* restore EBP */ \
  24. 52 "popfl\n" /* restore flags */ \
  25. 53 \
  26. 54 /* output parameters */ \
  27. 55 : [prev_sp] "=m" (prev->thread.sp), \
  28. 56 [prev_ip] "=m" (prev->thread.ip), \
  29. 57 "=a" (last), \
  30. 58 \
  31. 59 /* clobbered output registers: */ \
  32. 60 "=b" (ebx), "=c" (ecx), "=d" (edx), \
  33. 61 "=S" (esi), "=D" (edi) \
  34. 62 \
  35. 63 __switch_canary_oparam \
  36. 64 \
  37. 65 /* input parameters: */ \
  38. 66 : [next_sp] "m" (next->thread.sp), \
  39. 67 [next_ip] "m" (next->thread.ip), \
  40. 68 \
  41. 69 /* regparm parameters for __switch_to(): */ \
  42. 70 [prev] "a" (prev), \
  43. 71 [next] "d" (next) \
  44. 72 \
  45. 73 __switch_canary_iparam \
  46. 74 \
  47. 75 : /* reloaded segment registers */ \
  48. 76 "memory"); \
  49. 77} while (0)

 

首先在当前进程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进程开始执行。

进程上下文及与中断上下文切换的关系

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

三、总结

linux执行过程

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