AIOT人人都是极客

文章数:1086 被阅读:1552441

账号入驻

do_fork 的实现

最新更新时间:2021-06-21
    阅读数:

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

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

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

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

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

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
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

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

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:当用户态的进程发生异常(系统调用,中断等)进入内核态时,用来保存用户态进程的寄存器状态
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, 0sizeof(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, 0sizeof(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 则是指向内核栈的栈底处

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




5T技术资源大放送!包括但不限于:C/C++,Arm, Linux,Android,人工智能,单片机,树莓派,等等。在公众号内回复「peter」,即可免费获取!!


 记得点击分享在看,给我充点儿电吧

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: TI培训

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved