Chinaunix首页 | 论坛 | 博客
  • 博客访问: 48726
  • 博文数量: 7
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 304
  • 用 户 组: 普通用户
  • 注册时间: 2015-03-08 20:51
个人简介

爱学习不寂寞

文章分类
文章存档

2015年(7)

我的朋友

分类: LINUX

2015-04-12 23:26:48


概述

在《Linux系统调用的工作机制(上)》中分析了系统调用的工作过程,本文以Linux系统中创建进程的系统调用为例,分析创建一个新进程的具体过程。

如何创建进程

系统调用过程中会从用户态陷入内核态,在内核态中调用相应的系统调用服务例程,完成处理后从中断处理程序返回到用户态。Linux中fork, vfork和clone三个系统调用都可以创建一个新进程,在内核态的系统调用服务例程中都调用do_fork()来实现进程的创建,主要的函数调用关系为:
do_fork() -> copy_process() -> dup_task_struct().
三个函数都定义在内核源代码kernel/fork.c文件中,其中do_fork()的定义如下:

点击(此处)折叠或打开

  1. long do_fork(unsigned long clone_flags,
  2.      unsigned long stack_start,
  3.      unsigned long stack_size,
  4.      int __user *parent_tidptr,
  5.      int __user *child_tidptr)
  6. {
  7.     struct task_struct *p;
  8.     int trace = 0;
  9.     long nr;

  10.     /*
  11.      * Determine whether and which event to report to ptracer. When
  12.      * called from kernel_thread or CLONE_UNTRACED is explicitly
  13.      * requested, no event is reported; otherwise, report if the event
  14.      * for the type of forking is enabled.
  15.      */
  16.     if (!(clone_flags & CLONE_UNTRACED)) {
  17.         if (clone_flags & CLONE_VFORK)
  18.             trace = PTRACE_EVENT_VFORK;
  19.         else if ((clone_flags & CSIGNAL) != SIGCHLD)
  20.             trace = PTRACE_EVENT_CLONE;
  21.         else
  22.             trace = PTRACE_EVENT_FORK;

  23.         if (likely(!ptrace_event_enabled(current, trace)))
  24.             trace = 0;
  25.     }

  26.     p = copy_process(clone_flags, stack_start, stack_size,
  27.              child_tidptr, NULL, trace);
  28.     /*
  29.      * Do this prior waking up the new thread - the thread pointer
  30.      * might get invalid after that point, if the thread exits quickly.
  31.      */
  32.     if (!IS_ERR(p)) {
  33.         struct completion vfork;
  34.         struct pid *pid;

  35.         trace_sched_process_fork(current, p);

  36.         pid = get_task_pid(p, PIDTYPE_PID);
  37.         nr = pid_vnr(pid);

  38.         if (clone_flags & CLONE_PARENT_SETTID)
  39.             put_user(nr, parent_tidptr);

  40.         if (clone_flags & CLONE_VFORK) {
  41.             p->vfork_done = &vfork;
  42.             init_completion(&vfork);
  43.             get_task_struct(p);
  44.         }

  45.         wake_up_new_task(p);

  46.         /* forking complete and child started to run, tell ptracer */
  47.         if (unlikely(trace))
  48.             ptrace_event_pid(trace, pid);

  49.         if (clone_flags & CLONE_VFORK) {
  50.             if (!wait_for_vfork_done(p, &vfork))
  51.                 ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
  52.         }

  53.         put_pid(pid);
  54.     } else {
  55.         nr = PTR_ERR(p);
  56.     }
  57.     return nr;
  58. }
第29行调用copy_process()复制父进程的相关信息创建子进程的进程描述符struct task_struct,包括复制寄存器值和必要的数据结构;
第53行根据进程优先级将创建的子进程放入CPU的调度队列,该进程进入就绪状态。

copy_process()
函数代码较多,其中主要调用的函数有:
dup_task_struct()创建新的进程描述符task_struct;
copy_semundo()复制父进程的semaphore undo_list到子进程;
copy_files()和copy_fs()复制父进程文件系统相关的环境到子进程;
copy_sighand()和copy_signal()复制父进程信号处理相关的环境到子进程;
copy_mm()复制父进程内存管理相关的环境到子进程,包括页表、地址空间和代码数据;
copy_thread()设置子进程的执行环境,如子进程运行时各CPU寄存器的值、子进程的kernel栈的起始地址;
sched_fork()设置子进程调度相关的参数,即子进程的运行CPU、初始时间片长度和静态优先级等。
fork系统调用的奇妙之处在于一次调用两次返回,两次返回分别返回到父进程和子进程,在copy_thread()函数中可以一窥玄机:

点击(此处)折叠或打开

  1. *childregs = *current_pt_regs(); //复制内核堆栈
  2. childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
  3.  
  4. p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
  5. p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址
从上面的代码可知,子进程的堆栈中保存的寄存器ax值设为0,从而子进程在fork调用中的返回值为0,并且其堆栈指针寄存器sp设置为其内核栈顶,指令指针寄存器ip与父进程相同,因为子进程和父进程共享只读的文本段,系统调用结束后返回到同样的地址继续执行。

dup_task_struct()的定义如下:

点击(此处)折叠或打开

  1. static struct task_struct *dup_task_struct(struct task_struct *orig)
  2. {
  3.     struct task_struct *tsk;
  4.     struct thread_info *ti;
  5.     int node = tsk_fork_get_node(orig);
  6.     int err;

  7.     tsk = alloc_task_struct_node(node);
  8.     if (!tsk)
  9.         return NULL;

  10.     ti = alloc_thread_info_node(tsk, node);
  11.     if (!ti)
  12.         goto free_tsk;

  13.     err = arch_dup_task_struct(tsk, orig);
  14.     if (err)
  15.         goto free_ti;

  16.     tsk->stack = ti;
  17. #ifdef CONFIG_SECCOMP
  18.     /*
  19.      * We must handle setting up seccomp filters once we're under
  20.      * the sighand lock in case orig has changed between now and
  21.      * then. Until then, filter must be NULL to avoid messing up
  22.      * the usage counts on the error path calling free_task.
  23.      */
  24.     tsk->seccomp.filter = NULL;
  25. #endif

  26.     setup_thread_stack(tsk, orig);
  27.     clear_user_return_notifier(tsk);
  28.     clear_tsk_need_resched(tsk);
  29.     set_task_stack_end_magic(tsk);

  30. #ifdef CONFIG_CC_STACKPROTECTOR
  31.     tsk->stack_canary = get_random_int();
  32. #endif

  33.     /*
  34.      * One for us, one for whoever does the "release_task()" (usually
  35.      * parent)
  36.      */
  37.     atomic_set(&tsk->usage, 2);
  38. #ifdef CONFIG_BLK_DEV_IO_TRACE
  39.     tsk->btrace_seq = 0;
  40. #endif
  41.     tsk->splice_pipe = NULL;
  42.     tsk->task_frag.page = NULL;

  43.     account_kernel_stack(ti, 1);

  44.     return tsk;

  45. free_ti:
  46.     free_thread_info(ti);
  47. free_tsk:
  48.     free_task_struct(tsk);
  49.     return NULL;
  50. }
第8行alloc_task_struct_node()用于为新的task_struct分配内存并返回;
第12行alloc_thread_info_node()为thread_info结构分配内存并返回;
第16行arch_dup_task_struct()用于复制父进程的task_struct结构;
第20行将子进程的task_struct结构中stack指针指向其thread_info,从进程描述符中可查找到内核栈的地址;
第31行setup_thread_stack()用于设置内核栈中task指针指向task_struct,从而可从内核栈查找到其进程描述符task_struct。
第34行set_task_stack_end_magicy用于设置栈底魔数,用于放置内核栈溢出。

参考

《Linux系统调用的工作机制(上    http://blog.chinaunix.net/uid-30156195-id-4923751.html
孟宁 《Linux内核分析讲义》                


阅读(2764) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~