Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1025822
  • 博文数量: 26
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 437
  • 用 户 组: 普通用户
  • 注册时间: 2019-09-08 12:19
个人简介

关于个人介绍,既然你诚心诚意的看了 我就大发慈悲的告诉你 为了防止世界被破坏 为了维护世界的和平 贯彻爱与真实的邪恶 可爱又迷人的反派角色 我们是穿梭在银河的火箭队 白洞,白色的明天在等着我们

文章分类

分类: LINUX

2019-10-18 12:27:27

<Linux内核之execve函数>中,我们知道execve是一个新程序运行的开始,那么很多要问fork呢?fork又有什么区别呢?fork通常是在编程中,为了创建多个进程而使用的API。他和execve的差别是:execve属于进程替换,通过<Linux内核之execve函数>分析,仔细的朋友应该发现了execve函数在启动新程序的时候,还是用的之前老程序的task_struct结构。因此,这样它的pid就不会变,只是需要从内存重新分配他需要的页表和内存。而fork则不一样,它会产生一个完全独立的新进程,新的task_struct结构,新的pid的,只是会有一个写时复制的功能(刚刚fork出来的进程和原进程一模一样,内存什么都一样,只有当前进程内存发生写访问的时候,会重建立新的映射表)。这里先不说映射表重映射的问题(COW机制),这个问题之后讲映射的时候会讲。

下面我们看看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.     // stack_start是新程序占地址,stack_size栈大小
  8.     return _do_fork(clone_flags, stack_start, stack_size,
  9.             parent_tidptr, child_tidptr, 0);
  10. }

  11. SYSCALL_DEFINE0(vfork)
  12. {   // 可以看到vfork产生的新进程内存空间和原始进程一样CLONE_VM 
  13.     return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0, 0, NULL, NULL, 0);
  14. }

  15. SYSCALL_DEFINE0(fork)
  16. {
  17.     return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
  18. }

  19. // 内核线程创建,也是调用的do_fork,只是参数不同
  20. pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
  21. {
  22.     // 注意,内核线程在stack_start和stack_sz传入的是线程回调函数和线程传参
  23.     return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
  24.                     (unsigned long)arg, NULL, NULL, 0);
  25. }

其中SYSCALL_DEFINE0是一个宏,表示这是一个不包含任何参数的系统调用,这个宏会产生一个sys_fork/sys_vfork函数。也就是当应用程序调用fork函数的时候,CPU产生系统调用中断,中断处理函数通过查表(sys_call_table),通过对应的系统调用号找到sys_fork函数并开始执行(R7/W8存放的系统调用号,参数以此类推),sys_fork函数到_do_fork并没有做什么操作,只是参数不同(fork:SIGCHLD, vfork:CLONE_VFORK),这里kernel_thread、线程功能也会混着一起讲,因此现在直接到_do_fork函数,调用栈如下:
点击(此处)折叠或打开

  1. fork                 (user space)
  2. ---|----------------------------------------------------------------------------
  3.    v                (kernel space)
  4. el0_sync (arm64同步异常中断处理函数)
  5.     el0_svc (查找sys_call_table,获取到sys_execve函数地址,并运行)
  6.         sys_fork
  7.             do_fork
  8.                 _do_fork

_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.      unsigned long tls)
  7. {
  8.     struct task_struct *p;
  9.     int trace = 0;
  10.     long nr;
  11.     
  12.     // ptrace相关设置,主要判断进入do_fork是哪个系统调用引起的
  13.     if (!(clone_flags & CLONE_UNTRACED)) {
  14.         if (clone_flags & CLONE_VFORK)
  15.             trace = PTRACE_EVENT_VFORK;
  16.         else if ((clone_flags & CSIGNAL) != SIGCHLD)
  17.             trace = PTRACE_EVENT_CLONE;
  18.         else
  19.             trace = PTRACE_EVENT_FORK;

  20.         if (likely(!ptrace_event_enabled(current, trace)))
  21.             trace = 0;
  22.     }
  23.     // 将当前进程的进程空间拷贝一份给p,p是新进程的task_struct
  24.     p = copy_process(clone_flags, stack_start, stack_size,
  25.              child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
  26.     add_latent_entropy();
  27.     // 如果进程子进程复制成功,主进程就进入if语句,子进程不会进入这里,子进程直接进入
  28.     // ret_from_fork
  29.     if (!IS_ERR(p)) {
  30.         struct completion vfork;
  31.         struct pid *pid;

  32.         trace_sched_process_fork(current, p);
  33.         // 父进程获取新进程p的pid
  34.         pid = get_task_pid(p, PIDTYPE_PID);
  35.         nr = pid_vnr(pid);

  36.         if (clone_flags & CLONE_PARENT_SETTID)
  37.             put_user(nr, parent_tidptr);
  38.         // 查看父进程是否调用的vfork,如果是就初始化vfork_done完成函数
  39.         if (clone_flags & CLONE_VFORK) {
  40.             p->vfork_done = &vfork;
  41.             init_completion(&vfork);
  42.             get_task_struct(p);
  43.         }
  44.         // 将新进程p加入到run queue即加入到ready调度队列,此后p就会开始后参与调度
  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.         // 如果是vfork产生的进程,需要等待子进程先运行,直到子进程complete了父进程,
  50.         // vfork函数才返回。因此,vfork产生的子进程,总是优先于父进程运行。

  51.         //  vfork保证子进程先运行,在它调用exec或exit之后父进程才能调度运行,也就是当子
  52.         // 进程调用exec或者exit的时候, 会触发wake_up($vfork)的信号。
  53.         if (clone_flags & CLONE_VFORK) {
  54.             if (!wait_for_vfork_done(p, &vfork))
  55.                 ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
  56.         }

  57.         put_pid(pid);
  58.     } else {
  59.         nr = PTR_ERR(p);
  60.     }
  61.     // 父进程在这里返回,返回子进程的pid号,这个pid会修改父进程返回的pt_regs[0]的值
  62.     // 子进程的返回值在copy_thread_tls里面已经设置,子进程不会运行这里
  63.     return nr;
  64. }

可以知道,fork是把当前进程复制一个一模一样的进程,除了栈、LR,其他内容都一样。

在do_fork函数中实现的wake_up_new_task函数在copy_process函数之后调用,主要是用于将子进程加入调度队列。它的实现如下:

点击(此处)折叠或打开

  1. void wake_up_new_task(struct task_struct *p)
  2. {
  3.     struct rq_flags rf;
  4.     struct rq *rq;

  5.     raw_spin_lock_irqsave(&p->pi_lock, rf.flags);
  6.     // 设置新进程P的状态为TASK_RUNNING
  7.     p->state = TASK_RUNNING;
  8. #ifdef CONFIG_SMP
  9.     // 进程添加到CPU负载均衡
  10.     __set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));
  11. #endif
  12.     // 获取当前p进程所在CPU的运行队列rq
  13.     rq = __task_rq_lock(p, &rf);
  14.     post_init_entity_util_avg(&p->se);
  15.     // 将进程p加入到rq队列,具体在以后进程调度章节说明(RUNNING状态)
  16.     activate_task(rq, p, 0);
  17.     p->on_rq = TASK_ON_RQ_QUEUED;
  18.     trace_sched_wakeup_new(p);
  19.     check_preempt_curr(rq, p, WF_FORK);
  20.    
  21. #ifdef CONFIG_SMP
  22.     // SMP相关,这里占时不知道干什么的
  23.     if (p->sched_class->task_woken) {
  24.         p->sched_class->task_woken(rq, p);
  25.     }
  26. #endif
  27.     task_rq_unlock(rq, p, &rf);
  28. }
在do_fork函数中的copy_process函数是今天的重点函数,该函数实现了进程复制的所有操作,实现如下:
点击(此处)折叠或打开
  1. static __latent_entropy struct task_struct *copy_process(
  2.                     unsigned long clone_flags,
  3.                     unsigned long stack_start,
  4.                     unsigned long stack_size,
  5.                     int __user *child_tidptr,
  6.                     struct pid *pid,
  7.                     int trace,
  8.                     unsigned long tls,
  9.                     int node)
  10. {
  11.     int retval;
  12.     struct task_struct *p;

  13.     // 这里省略掉一些参数检测
  14.     
  15.     // 将当前进程复制一份给新进程p
  16.     retval = -ENOMEM;
  17.     p = dup_task_struct(current, node);
  18.     if (!p)
  19.         goto fork_out;

  20.     ftrace_graph_init_task(p);

  21.     rt_mutex_init_task(p);

  22.     // 省略一些无用,chinaunix总说是敏感词汇的代码。

  23.     // 复制信用凭证
  24.     retval = copy_creds(p, clone_flags);
  25.     if (retval < 0)
  26.         goto bad_fork_free;
  27.     // 超过最大进程,返回
  28.     retval = -EAGAIN;
  29.     if (nr_threads >= max_threads)
  30.         goto bad_fork_cleanup_count;

  31.     delayacct_tsk_init(p);    /* Must remain after dup_task_struct() */
  32.     p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
  33.     p->flags |= PF_FORKNOEXEC;
  34.     INIT_LIST_HEAD(&p->children);
  35.     INIT_LIST_HEAD(&p->sibling);
  36.     rcu_copy_process(p);
  37.     p->vfork_done = NULL;
  38.     spin_lock_init(&p->alloc_lock);
  39.     // 初始化信号挂起pending
  40.     init_sigpending(&p->pending);

  41.     // 这里省略初始化和时间等相关的杂项

  42.     // 初始化调度实体se,和调度器类,调度器章节将会描述
  43.     // 如果存在p->sched_class->task_fork,就执行
  44.     retval = sched_fork(clone_flags, p);
  45.     if (retval)
  46.         goto bad_fork_cleanup_policy;
  47.     // perf相关,这里不描述
  48.     retval = perf_event_init_task(p);
  49.     if (retval)
  50.         goto bad_fork_cleanup_policy;
  51.     //审计相关,这里不描述
  52.     retval = audit_alloc(p);
  53.     if (retval)
  54.         goto bad_fork_cleanup_perf;
  55.     // 进程共享内存链表初始化
  56.     shm_init_task(p);
  57.     retval = copy_semundo(clone_flags, p);
  58.     if (retval)
  59.         goto bad_fork_cleanup_audit;
  60.     // 将当前current进程的打开的fd描述复制一份到新进程->p->files
  61.     retval = copy_files(clone_flags, p);
  62.     if (retval)
  63.         goto bad_fork_cleanup_semundo;
  64.     // 将current进程的工作目录复制一份到新进程(pwd, /)-》p->fs
  65.     retval = copy_fs(clone_flags, p);
  66.     if (retval)
  67.         goto bad_fork_cleanup_files;
  68.     // 将Current进程的信号处理函数,赋值一份到新进程->p->sighand
  69.     retval = copy_sighand(clone_flags, p);
  70.     if (retval)
  71.         goto bad_fork_cleanup_fs;
  72.     // 通过current初始化一份信号共享结构(线程信号共享就用的这个结构,还有一些其他数据
  73.     // p->signal
  74.     retval = copy_signal(clone_flags, p);
  75.     if (retval)
  76.         goto bad_fork_cleanup_sighand;
  77.     // 将current的映射页表拷贝一份到新进程,并设置成只读,为什么这么设置,
  78.     // 以后的虚拟内存章节会讲解。
  79.     // 即这就是为什么刚fork后,两个进程内存里面的值是一样的
  80.     retval = copy_mm(clone_flags, p);
  81.     if (retval)
  82.         goto bad_fork_cleanup_signal;
  83.     // 命名空间继承current的,即同一个命名空间
  84.     retval = copy_namespaces(clone_flags, p);
  85.     if (retval)
  86.         goto bad_fork_cleanup_mm;
  87.     // 赋值IO空间,tsk->io_context或者new_ioc->ioprio
  88.     retval = copy_io(clone_flags, p);
  89.     if (retval)
  90.         goto bad_fork_cleanup_namespaces;
  91.     // 拷贝每个进程不同的部分,这里非常重要,稍后详细说明
  92.     retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls);
  93.     if (retval)
  94.         goto bad_fork_cleanup_io;

  95.     // 如果线程不是init_struct_pid(idle进程pid,就从新申请一个Pid
  96.     if (pid != &init_struct_pid) {
  97.         pid = alloc_pid(p->nsproxy->pid_ns_for_children);
  98.         if (IS_ERR(pid)) {
  99.             retval = PTR_ERR(pid);
  100.             goto bad_fork_cleanup_thread;
  101.         }
  102.     }
  103.     //省略一些赋值

  104.     // 如果是线程,则设置线程组组长为当前进程指向的组长,如果是进程则将组长设置成自己,
  105.     // 并初始化退出码
  106.     p->pid = pid_nr(pid);
  107.     if (clone_flags & CLONE_THREAD) // 线程运行这里
  108.         p->exit_signal = -1;
  109.         p->group_leader = current->group_leader;
  110.         p->tgid = current->tgid// 进程的tgid才是真的pid 即同一个线程组下的线程tgid相同,pid不同
  111.     } else {
  112.         if (clone_flags & CLONE_PARENT)
  113.             p->exit_signal = current->group_leader->exit_signal;
  114.         else
  115.             p->exit_signal = (clone_flags & CSIGNAL);
  116.         p->group_leader = p;
  117.         p->tgid = p->pid//没有线程时,tgid==pid
  118.     }

  119.     p->nr_dirtied = 0;
  120.     p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10);
  121.     p->dirty_paused_when = 0;

  122.     p->pdeath_signal = 0;
  123.     INIT_LIST_HEAD(&p->thread_group);
  124.     p->task_works = NULL;

  125.     // cgroup相关,这里不说明
  126.     threadgroup_change_begin(current);
  127.     retval = cgroup_can_fork(p);
  128.     if (retval)
  129.         goto bad_fork_free_pid;

  130.     write_lock_irq(&tasklist_lock);

  131.     // 设置父进程,如果是线程,父进程就是current的父进程。否则current就是新进程的父进程
  132.     if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
  133.         p->real_parent = current->real_parent;
  134.         p->parent_exec_id = current->parent_exec_id;
  135.     } else {
  136.         p->real_parent = current;
  137.         p->parent_exec_id = current->self_exec_id;
  138.     }

  139.     // 省略一些与此无关的

  140.     // 
  141.     if (likely(p->pid)) {
  142.         ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);

  143.         //初始化pid结构到
  144.         init_task_pid(p, PIDTYPE_PID, pid);
  145.         if (thread_group_leader(p)) {//新进程是线程组组长,同样意味着它是进程 而不是线程
  146.            init_task_pid(p, PIDTYPE_PGID, task_pgrp(current));//将pid赋值到task->pid[PGID].pid
  147.           init_task_pid(p, PIDTYPE_SID, task_session(current));//将pid赋值到task->pid[SID].pid

  148.             // 是否是进程收养者,是,就设置成当前Pid空间的收养者(do_exit函数里面会用到
  149.             if (is_child_reaper(pid)) {
  150.                 ns_of_pid(pid)->child_reaper = p;
  151.                 p->signal->flags |= SIGNAL_UNKILLABLE;
  152.             }

  153.             p->signal->leader_pid = pid;//因为是组长,因此将它的pid记录到leader_pid
  154.             p->signal->tty = tty_kref_get(current->signal->tty);
  155.             // 将进程p添加到它父进程的children链表
  156.             list_add_tail(&p->sibling, &p->real_parent->children);
  157.             // 将进程p添加到Init_task下面,即idle进程的tasks链表下面
  158.             list_add_tail_rcu(&p->tasks, &init_task.tasks);
  159.             // 将p添加到对应的pgid和sid链表(组,会话
  160.             attach_pid(p, PIDTYPE_PGID);
  161.             attach_pid(p, PIDTYPE_SID);
  162.             __this_cpu_inc(process_counts);
  163.         } else {// 不是组长,说明是线程,增加线程数量
  164.             current->signal->nr_threads++;
  165.             atomic_inc(&current->signal->live);//增加线程live数量
  166.             atomic_inc(&current->signal->sigcnt);
  167.             list_add_tail_rcu(&p->thread_group// 将线程p挂到组长的thread_group链表下面
  168.                      &p->group_leader->thread_group);
  169.             list_add_tail_rcu(&p->thread_node// 将线程p挂到共享信号的thread_head链表下面(信号使用
  170.                      &p->signal->thread_head);
  171.         }
  172.         // 添加到pid空间,并增加线程数
  173.         attach_pid(p, PIDTYPE_PID);
  174.         nr_threads++;
  175.     }

  176.     total_forks++;
  177.     //省略一些不重要的

  178.     // 返回新进程task_struct
  179.     return p;
  180.     // 省略失败处理细节
  181. }

在copy_process函数中的dup_task_struct函数实现如下,该函数用于从当前进程current的task_struct结构中,复制一份一模一样的给新进程p:
点击(此处)折叠或打开

  1. static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
  2. {
  3.     struct task_struct *tsk;
  4.     unsigned long *stack;
  5.     struct vm_struct *stack_vm_area;
  6.     int err;
  7.     
  8.     // 这里传入的参数是NUMA_NO_NODE,表示申请内存的时候,从当前进程所在内存结点申请;内存结点将在伙伴子系统加以说明,这里不讲解;
  9.     if (node == NUMA_NO_NODE)
  10.         node = tsk_fork_get_node(orig);

  11.     // 从内存node结点申请一段内存作为新进程的tsk结构
  12.     tsk = alloc_task_struct_node(node);
  13.     if (!tsk)
  14.         return NULL;

  15.     // 从node结点申请一个新的栈地址
  16.     stack = alloc_thread_stack_node(tsk, node);
  17.     if (!stack)
  18.         goto free_tsk;

  19.     // 得到一个stack的vm_struct结构,这里不讲解
  20.     stack_vm_area = task_stack_vm_area(tsk);

  21.     // 这个函数 其实就是tsk = orig,即将current进程memcpy到tsk。
  22.     err = arch_dup_task_struct(tsk, orig);

  23.     // 将stack地址设置到tsk的记录栈地址的位置,替换掉从current拷贝来的栈(这是内核栈,
  24.     // 每个应用程序有两个栈,一个应用栈,一个内核栈;而内核线程只有内核栈.
  25.     // 因此,这里可以统一申请内核栈)
  26.     tsk->stack = stack;
  27.     // 如果定义了VMAP_STACK,就将stack vm_struct结构赋值
  28. #ifdef CONFIG_VMAP_STACK
  29.     tsk->stack_vm_area = stack_vm_area;
  30. #endif
  31.     // 增加引用计数
  32. #ifdef CONFIG_THREAD_INFO_IN_TASK
  33.     atomic_set(&tsk->stack_refcount, 1);
  34. #endif

  35.     if (err)
  36.         goto free_stack;

  37. #ifdef CONFIG_SECCOMP
  38.     tsk->seccomp.filter = NULL;
  39. #endif
  40.     // 将current的threadinfo结构拷贝一份到tsk,threadinfo待会儿会详细介绍。
  41.     setup_thread_stack(tsk, orig);
  42.     clear_user_return_notifier(tsk);
  43.     clear_tsk_need_resched(tsk);
  44.     // 设置栈的结束位置,用于检测栈溢出用的
  45.     set_task_stack_end_magic(tsk);

  46.     // 省略一些无关紧要的赋值

  47.     account_kernel_stack(tsk, 1);

  48.     kcov_task_init(tsk);
  49.     // 返回新进程的task_struct结构
  50.     return tsk;
  51. }

  52. static inline void setup_thread_stack(struct task_struct *p, struct task_struct *org)
  53. {
  54.     // 赋值current的thread_info结构,然后将task指向新进程
  55.     *task_thread_info(p) = *task_thread_info(org);
  56.     task_thread_info(p)->task = p;
  57. }

在申请新的进程task_struct结束后,我这里有必要说说进程的thread_info结构。thread_info结构是存放到内核的SP栈中的,原本Linux是直接将task_struct存放到SP当中,但是后来随着task_struct越来越大,因此,添加了一个thread_info结构做中间代理结构,达到通过sp能够获取到对应进程的task_struct结构的目的。thread_info在栈的位置如图:

如上图, 通常内核的栈大小为2个page,即8K,并且内核栈严格按照8K对齐,因此当获取到SP的指针的时候(sp current),只需要与上8k取反的值(sp & (~8k))就可以得到thread_info结构。thread_info->task就指向该进程的task_struct结构。由于内核栈是向下增长,因此,如果栈越界,首先破坏的是自己的thread_info结构,然后崩溃(咋们内核中使用的current起始就是一个宏,利用当前SP和thread_info反向找到自己的task_struct结构的,有兴趣的可以看看这个宏)。

copy_process函数中实现的copy_files函数比较简单,如下:

点击(此处)折叠或打开

  1. static int copy_files(unsigned long clone_flags, struct task_struct *tsk)
  2. {
  3.     struct files_struct *oldf, *newf;
  4.     int error = 0;

  5.     oldf = current->files;
  6.     if (!oldf)
  7.         goto out;

  8.     if (clone_flags & CLONE_FILES) {
  9.         atomic_inc(&oldf->count);
  10.         goto out;
  11.     }
  12.     // 复制一份current的files成newf
  13.     newf = dup_fd(oldf, &error);
  14.     if (!newf)
  15.         goto out;
  16.     // 将复制出来的newf赋值给files
  17.     tsk->files = newf;
  18.     error = 0;
  19. out:
  20.     return error;
  21. }

因此,从copy_files这一个简单的说明后,其他的copy_*函数,这里就不多做说明了, 有兴趣的,可以自己去看看,先主要说说copy_thread_tls函数

copy_thread_tls是一个非常重要的函数,其主要是设置新进程的运行地址和寄存器值,实现如下:

点击(此处)折叠或打开

  1. struct cpu_context {
  2. unsigned long x19;
  3. unsigned long x20;
  4. unsigned long x21;
  5. unsigned long x22;
  6. unsigned long x23;
  7. unsigned long x24;
  8. unsigned long x25;
  9. unsigned long x26;
  10. unsigned long x27;
  11. unsigned long x28;
  12. unsigned long fp;
  13. unsigned long sp;
  14. unsigned long pc;
    };

  15. asmlinkage void ret_from_fork(void) asm("ret_from_fork");

  16. // 这个函数是和体系结构强相关的,这里我看的是armv8处理器
  17. int copy_thread(unsigned long clone_flags, unsigned long stack_start,
  18.         unsigned long stk_sz, struct task_struct *p)
  19. {
  20.     // 获取pt_regs地址: task->stack + THREAD_START_SP -1
  21.     // 其中task->stack起始存放的是thread_info,THREAD_START_SP = 16k - 16,
  22.     // 即在栈的最高地址存放的是寄存器值, pt_regs
  23.     struct pt_regs *childregs = task_pt_regs(p);

  24.     // 将新进程的内核态需要的寄存器信息清0
  25.     memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context));

  26.     // 发现新进程是一个应用进程,进入if语句
  27.     if (likely(!(p->flags & PF_KTHREAD))) {
  28.         // 复制current进程的用户态寄存器到新进程
  29.         *childregs = *current_pt_regs();
  30.         // 将新进程的返回值设置成0,regs[0] 就是X0寄存器,在ARM中X0作为返回值寄存器使用
  31.         // 因此当fork函数返回后,在子进程中,返回值是0
  32.         childregs->regs[0] = 0;

  33.    
  34.         *task_user_tls(p) = read_sysreg(tpidr_el0);
  35.         // 如果用户设置了栈的起始地址,就是设置用户栈的地址是stack_start
  36.         // 用户进程有两个栈,用户态的栈和内核态的栈,内核态的栈前面dup_task_struct的
  37.         // 时候申请了,这里是设置用户态的栈,由用户态指定
  38.         if (stack_start) {
  39.             if (is_compat_thread(task_thread_info(p)))
  40.                 childregs->compat_sp = stack_start;
  41.             else
  42.                 childregs->sp = stack_start;
  43.         }

  44.         // TLS相关的
  45.         if (clone_flags & CLONE_SETTLS)
  46.             p->thread.tp_value = childregs->regs[3];
  47.     } else // 内核线程进入else语句
  48.         // 内核线程将childregs的CPU寄存器请0
  49.         memset(childregs, 0, sizeof(struct pt_regs));
  50.         childregs->pstate = PSR_MODE_EL1h;//设置CPU工作模式是EL1模式(用户进程在EL0模式
  51.         if (IS_ENABLED(CONFIG_ARM64_UAO) &&
  52.          cpus_have_cap(ARM64_HAS_UAO))
  53.             childregs->pstate |= PSR_UAO_BIT;
  54.         // X19存放stack_start,从kernel_thread传入参数可以知道stack_start是线程回调函数
  55.         // X20存放内核线程传入参数(stk_sz:内核线程传入的线程函数传参
  56.         p->thread.cpu_context.x19 = stack_start;
  57.         p->thread.cpu_context.x20 = stk_sz;
  58.     }
  59.     // 不管是内核线程还是应用线程,新进程执行的第一个函数时钟是ret_from_fork
  60.     p->thread.cpu_context.pc = (unsigned long)ret_from_fork;
  61.     // 设置CPU的栈指向childregs结构,方便返回的时候,弹出到寄存器。
  62.     p->thread.cpu_context.sp = (unsigned long)childregs;

  63.     ptrace_hw_copy_thread(p);

  64.     return 0;
  65. }

  66. static inline int copy_thread_tls(
  67.         unsigned long clone_flags, unsigned long sp, unsigned long arg,
  68.         struct task_struct *p, unsigned long tls)
  69. {
  70.     return copy_thread(clone_flags, sp, arg, p);
  71. }

经过了copy_thread_tls函数之后,新进程的运行环境就已经准备就绪,最后只需要将新进程的task_struct结构加入到run queue调度队列里面即可(wake_up_new_task函数)。一旦调度到新的进程,新的进程就从ret_from_fork函数开始运行,其中ret_from_fork函数实现如下:
点击(此处)折叠或打开

  1. ret_to_user:
  2.     disable_irq   // 禁止中断
  3.     ldr    x1, [tsk, #TI_FLAGS// 读取tsk结构的TI_FLAGS位到x1寄存器
  4.     and    x2, x1, #_TIF_WORK_MASK  // 判断tsk的TI_FLAGS位是否置位
  5.     cbnz    x2, work_pending  // 这里用于检查是否有工作挂起。
  6. finish_ret_to_user:
  7.     enable_step_tsk x1, x2
  8.     kernel_exit 0     // 退出内核态EL1到EL0
  9. ENDPROC(ret_to_user)

  10. ENTRY(ret_from_fork)
  11.     bl    schedule_tail  // 进程调度的时候 会说明
  12.     cbz    x19, 1f  //从copy_thread_tls函数知道x19如果不为0那么,此进程就为内核线程。X19存放的是内核线程的回调函数。
  13.     mov    x0, x20 //从copy_thread_tls函数知道,如果是内核线程,那么x20内核线程的回调函数的传入参数
  14.     blr    x19  // x0存放传入的第一个参数,然后跳转到内核线程回调函数去执行。
  15. 1:    get_thread_info tsk  // 用户线程运行这里,通过ret_to_user返回到用户空间
  16.     b    ret_to_user   // 返回到应用空间
  17. ENDPROC(ret_from_fork)

应用程序fork的调用栈如下:

注:copy_mm函数会在以后讲解虚拟地址的时候详细讲解。


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