前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >do_fork 的实现

do_fork 的实现

作者头像
刘盼
发布2021-07-05 20:45:21
6660
发布2021-07-05 20:45:21
举报
文章被收录于专栏:人人都是极客人人都是极客

上面讲述了如何通过 fork, vfork, pthread_create 去创建一个进程,或者一个线程。通过分析最终 fork, vfork, pthread_create 最终都会通过系统调用 do_fork 去创建进程。

代码语言:javascript
复制
long _do_fork(unsigned long clone_flags,
       unsigned long stack_start,
       unsigned long stack_size,
       int __user *parent_tidptr,
       int __user *child_tidptr,
       unsigned long tls)
{
  ......
  p = copy_process(clone_flags, stack_start, stack_size,
    child_tidptr, NULL, trace, tls, NUMA_NO_NODE);  ------(1)
  ......
  pid = get_task_pid(p, PIDTYPE_PID);               ------(2)
  ......
  wake_up_new_task(p);                              ------(3)
}
  1. 创建一个进程的主要函数,里面功能主要是负责拷贝父进程的相关资源。返回值是一个 task_struct 指针
  2. 给上面创建的子进程分配一个 pid
  3. 将子进程加入到就绪队列中去,至于何时被调度是调度器说了算

copy_process

代码语言:javascript
复制
static __latent_entropy struct task_struct *copy_process(
     unsigned long clone_flags,
     unsigned long stack_start,
     unsigned long stack_size,
     int __user *child_tidptr,
     struct pid *pid,
     int trace,
     unsigned long tls,
     int node)
{
  ......
  p = dup_task_struct(current, node);     ------(1)
  ......
  retval = sched_fork(clone_flags, p);    ------(2)
  ......
  retval = copy_files(clone_flags, p);    ------(3)
  ......
  retval = copy_fs(clone_flags, p);       ------(4)
  ......
  retval = copy_mm(clone_flags, p);       ------(5)
  ......
  retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls);  ------(6)
  ......
  if (pid != &init_struct_pid) {
    pid = alloc_pid(p->nsproxy->pid_ns_for_children);  ------(7)
    if (IS_ERR(pid)) {
      retval = PTR_ERR(pid);
      goto bad_fork_cleanup_thread;
    }
  }
  ......
}
  1. 为子进程创建一个新的 task_struct 结构,然后复制父进程的 task_struct 结构到子进程新创建的
  2. 初始化子进程调度相关的信息,并把进程状态设置为 TASK_NEW
  3. 复制进程的文件信息
  4. 复制进程的文件系统资源
  5. 复制进程的内存信息
  6. 复制进程的CPU体系相关的信息
  7. 为新进程分配新的pid

接下来我们一起看下这里的几个函数。

dup_task_struct

代码语言:javascript
复制
static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{
 struct task_struct *tsk;
 unsigned long *stack;
 struct vm_struct *stack_vm_area;
 int err;
 ......
 tsk = alloc_task_struct_node(node);          ------(1)
 if (!tsk)
  return NULL;

 stack = alloc_thread_stack_node(tsk, node);  ------(2)
 if (!stack)
  goto free_tsk;

 stack_vm_area = task_stack_vm_area(tsk);

 err = arch_dup_task_struct(tsk, orig);       ------(3)

 tsk->stack = stack;                          ------(4)
 ......
 setup_thread_stack(tsk, orig);               ------(5)
 clear_user_return_notifier(tsk);
 clear_tsk_need_resched(tsk);                 ------(6)
 ......
}
  1. 使用slub分配器,为子进程分配一个 task_struct 结构
  2. 为子进程分配内核栈
  3. 将父进程 task_struct 的内容复制给子进程的 task_struct
  4. 设置子进程的内核栈
  5. 建立 thread_info 和内核栈的关系
  6. 清空子进程需要调度的标志位

sched_fork

代码语言:javascript
复制
int sched_fork(unsigned long clone_flags, struct task_struct *p)
{
 unsigned long flags;
 int cpu = get_cpu();

 __sched_fork(clone_flags, p);         ------(1)
 /*
  * We mark the process as NEW here. This guarantees that
  * nobody will actually run it, and a signal or other external
  * event cannot wake it up and insert it on the runqueue either.
  */
 p->state = TASK_NEW;                  ------(2)

 /*
  * Make sure we do not leak PI boosting priority to the child.
  */
 p->prio = current->normal_prio;       ------(3)
 ......
 if (dl_prio(p->prio)) {
  put_cpu();
  return -EAGAIN;
 } else if (rt_prio(p->prio)) {
  p->sched_class = &rt_sched_class;
 } else {
  p->sched_class = &fair_sched_class;  ------(4)
 }
 ......
 init_task_preempt_count(p);           ------(5)
 ......
}
  1. 对 task_struct 中调度相关的信息进行初始化
  2. 把进程状态设置为 TASK_NEW, 表示这是一个新创建的进程
  3. 设置新创建进程的优先级,优先级是跟随当前进程的
  4. 设置进程的调度类为 CFS
  5. 初始化当前进程的preempt_count字段。此字段包含抢占使能,中断使能等

copy_mm

代码语言:javascript
复制
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
 struct mm_struct *mm, *oldmm;
 int retval;
 ......
 if (!oldmm)                    ------(1)  
  return 0;

 /* initialize the new vmacache entries */
 vmacache_flush(tsk);

 if (clone_flags & CLONE_VM) {  ------(2)  
  mmget(oldmm);
  mm = oldmm;
  goto good_mm;
 }

 retval = -ENOMEM;
 mm = dup_mm(tsk);              ------(3) 
 ......
}
  1. 如果当前进程的mm_struct结构为NULL,则当前进程是一个内核线程
  2. 如果设置了CLONE_VM,则新创建进程的 mm 和当前进程 mm 共享
  3. 重新分配一个mm_struct结构,将当前进程的mm_struct的内容做一次copy
代码语言:javascript
复制
static struct mm_struct *dup_mm(struct task_struct *tsk)
{
 struct mm_struct *mm, *oldmm = current->mm;
 int err;

 mm = allocate_mm();                  ------(1)
 if (!mm)
  goto fail_nomem;

 memcpy(mm, oldmm, sizeof(*mm));      ------(2)

 if (!mm_init(mm, tsk, mm->user_ns))  ------(3)
  goto fail_nomem;

 err = dup_mmap(mm, oldmm);           ------(4)
 if (err)
  goto free_pt;
 ......
}
  1. 重新分配一个 mm_struct 结构
  2. 做一次copy
  3. 对刚分配的 mm_struct 结构做初始化的操作,其中会为当前进程分配一个 pgd,基全局目录项
  4. 复制父进程的VMA对应的PTE页表项到子进程的页表项中

copy_thread_tls

在讲解这个函数之前,先看下几个重要的结构体,具体的用法会在进程调度章节中有详细描述。

代码语言:javascript
复制
struct task_struct {
    struct thread_info thread_info;
    ......
   /* CPU-specific state of this task: */
    struct thread_struct        thread;
}

struct cpu_context {
    unsigned long x19;
    unsigned long x20;
    unsigned long x21;
    unsigned long x22;
    unsigned long x23;
    unsigned long x24;
    unsigned long x25;
    unsigned long x26;
    unsigned long x27;
    unsigned long x28;
    unsigned long fp;
    unsigned long sp;
    unsigned long pc;
};

struct thread_struct {
    struct cpu_context    cpu_context;    /* cpu context */
 
    unsigned int        fpsimd_cpu;
    void            *sve_state;    /* SVE registers, if any */
    unsigned int        sve_vl;        /* SVE vector length */
    unsigned int        sve_vl_onexec;    /* SVE vl after next exec */
    unsigned long        fault_address;    /* fault info */
    unsigned long        fault_code;    /* ESR_EL1 value */
    struct debug_info    debug;        /* debugging */
};

struct user_pt_regs {
    __u64        regs[31];
    __u64        sp;
    __u64        pc;
    __u64        pstate;
};
 
struct pt_regs {
    union {
        struct user_pt_regs user_regs;
        struct {
            u64 regs[31];
            u64 sp;
            u64 pc;
            u64 pstate;
        };
    };
    u64 orig_x0;
#ifdef __AARCH64EB__
    u32 unused2;
    s32 syscallno;
#else
    s32 syscallno;
    u32 unused2;
#endif
 
    u64 orig_addr_limit;
    u64 unused;    // maintain 16 byte alignment
    u64 stackframe[2];
};
  • cpu_context:结构在进程切换时用来保存上一个进程的寄存器的值
  • thread_struct:在内核态两个进程发生切换时,用来保存上一个进程的相关寄存器
  • pt_regs:当用户态的进程发生异常(系统调用,中断等)进入内核态时,用来保存用户态进程的寄存器状态
代码语言:javascript
复制
int copy_thread(unsigned long clone_flags, unsigned long stack_start,
  unsigned long stk_sz, struct task_struct *p)
{
 struct pt_regs *childregs = task_pt_regs(p);                    ------(1)

 memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context));  ------(2)
 ......
 if (likely(!(p->flags & PF_KTHREAD))) {                         ------(3)
  *childregs = *current_pt_regs();                               ------(4)
  childregs->regs[0] = 0;                                        ------(5)
  ......
 } else {                                                        ------(6)
  memset(childregs, 0, sizeof(struct pt_regs));
  childregs->pstate = PSR_MODE_EL1h;                             ------(7)
  if (IS_ENABLED(CONFIG_ARM64_UAO) &&
      cpus_have_const_cap(ARM64_HAS_UAO))
   childregs->pstate |= PSR_UAO_BIT;
  p->thread.cpu_context.x19 = stack_start;                       ------(8)
  p->thread.cpu_context.x20 = stk_sz;                            ------(9)
 }
 p->thread.cpu_context.pc = (unsigned long)ret_from_fork;        ------(10)
 p->thread.cpu_context.sp = (unsigned long)childregs;            ------(11)

 ptrace_hw_copy_thread(p);

 return 0;
}
  1. 获取到新创建进程的 pt_regs 结构
  2. 将新创建进程的 thread_struct 结构清空
  3. 用户进程的情况
  4. 获取当前进程的 pt_regs
  5. 一般用户态通过系统调度陷入到内核态后处理完毕后会通过 x0 寄存器设置返回值的,这里首先将返回值设置为0
  6. 内核线程的情况
  7. 设置当前进程是 pstate 是在 EL1 模式下,ARM64 架构中使用 pstate 来描述当前处理器模式
  8. 创建内核线程的时候会传递内核线程的回调函数到 stack_start 的参数,将其设置到 x19 寄存器
  9. 创建内核线程的时候也会传递回调函数的参数,设置到 x20 寄存器
  10. 设置新创建进程的 pc 指针为 ret_from_fork,当新创建的进程运行时会从 ret_from_fork 运行,ret_from_fork 是个汇编语言编写的
  11. 设置新创建进程的 SP_EL1 的值为 childregs, SP_EL1 则是指向内核栈的栈底处

我们用一张图简单的总结下:

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-06-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 人人都是极客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • copy_process
  • dup_task_struct
  • sched_fork
  • copy_mm
  • copy_thread_tls
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档