澳门新蒲京娱乐

新蒲京娱乐场777 1
守护进程的启动方法,Linux守护进程的启动方法
图片 1
一万小时

Linux下进程的创建过程分析【新蒲京娱乐场777】,Linux内核是如何创建一个新进程的

经过描述

进程描述符(task_struct)

用来说述进程的数据结构,能够领略为经过的性格。例如进程的事态、进度的标记(PID)等,都被封装在了经过描述符这些数据构造中,该数据布局被定义为task_struct

进度序调节制块(PCB)

是操作系统大旨中一种数据布局,首要代表经过意况。

进度情形

新蒲京娱乐场777 1

fork()

fork(State of Qatar在父、子进度各重回二回。在父进度中再次回到子进度的
pid,在子进度中重返0。

fork二个子经过的代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
  int pid;
  /* fork another process */

  pid = fork();
  if (pid < 0) 
  { 
      /* error occurred */
      fprintf(stderr,"Fork Failed!");
      exit(-1);
  } 
  else if (pid == 0) 
  {
      /* child process */
      printf("This is Child Process!\n");
  } 
  else 
  {  
      /* parent process  */
      printf("This is Parent Process!\n");
      /* parent will wait for the child to complete*/
      wait(NULL);
      printf("Child Complete!\n");
  }
}

转自:

进度创立

前言


Unix规范的复制进度的类别调用时fork(即分叉),不过Linux,BSD等操作系统并不独有达成那叁个,确切的说linux完毕了八个,fork,vfork,clone(确切说vfork成立出来的是轻量级进度,也叫线程,是分享能源的进度)

系统调用 描述
fork fork创造的子进程是父进程的完整副本,复制了父亲进程的资源,包括内存的内容task_struct内容
vfork vfork创建的子进程与父进程共享数据段,而且由vfork()创建的子进程将先于父进程运行
clone Linux上创建线程一般使用的是pthread库 实际上linux也给我们提供了创建线程的系统调用,就是clone

有关顾客空间利用fork, vfork和clone, 请参见

Linux中fork,vfork和clone安详严整(不一样与联系)

fork, vfork和clone的种类调用的入口地址分别是sys_fork,
sys_vfork和sys_clone, 而他们的概念是借助于系统构造的,
因为在客户空间和基本空间之间传递参数的不二等秘书籍因种类布局而异

系统调用的参数字传送递

系统调用的贯彻与C库差别,
普通C函数通过将参数的值压入到进度的栈中举行参数的传递。由于系统调用是经过暂停进度从客商态到内核态的一种新鲜的函数调用,未有客商态大概内核态的库房能够被用来在调用函数和被调函数之间张开参数字传送递。系统调用通过CPU的寄放器来打开参数字传送递。在扩充系统调用以前,系统调用的参数被写入CPU的贮存器,而在事实上调用系统服务例程早先,内核将CPU存放器的剧情拷贝到内核仓库中,完成参数的传递。

由此差别的系统构造或许利用分裂的点子照旧不一样的寄放器来传递参数,而地点函数的天职正是从微处理器的寄放器中领到顾客空间提供的信息,
并调用系统布局非亲非故的_do_fork(也许初期的do_fork)函数,
负担进程的复制

区别的系统布局大概需求动用区别的章程照旧存放器来存款和储蓄函数调用的参数,
由此linux在安插系统调用的时候,
将其分割成体系构造有关的等级次序和系统布局非亲非故的层系,
前者复杂提收取信任与系统结构的特定的参数,
前面一个则基于参数的设置举办一定的实在操作

大约流程

fork
通过0×80暂停(系统调用)来陷入内核,由系统提供的相应系统调用来成功进度的创始。

fork.c

//fork
#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
    return do_fork(SIGCHLD, 0, 0, NULL, NULL);
#else
    /* can not support in nommu mode */
    return -EINVAL;
#endif
}
#endif

//vfork
#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
            0, NULL, NULL);
}
#endif

//clone
#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
         int __user *, parent_tidptr,
         int, tls_val,
         int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
         int __user *, parent_tidptr,
         int __user *, child_tidptr,
         int, tls_val)
#elif defined(CONFIG_CLONE_BACKWARDS3)
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
        int, stack_size,
        int __user *, parent_tidptr,
        int __user *, child_tidptr,
        int, tls_val)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
         int __user *, parent_tidptr,
         int __user *, child_tidptr,
         int, tls_val)
#endif
{
    return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
}
#endif

经过看上面的代码,大家能够精晓的见到,无论是行使 fork 照旧 vfork
来创立进度,最终都以通过 do_fork(卡塔尔(قطر‎ 方法来落到实处的。接下来大家能够追踪到
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)
{
        //创建进程描述符指针
        struct task_struct *p;

        //……

        //复制进程描述符,copy_process()的返回值是一个 task_struct 指针。
        p = copy_process(clone_flags, stack_start, stack_size,
             child_tidptr, NULL, trace);

        if (!IS_ERR(p)) {
            struct completion vfork;
            struct pid *pid;

            trace_sched_process_fork(current, p);

            //得到新创建的进程描述符中的pid
            pid = get_task_pid(p, PIDTYPE_PID);
            nr = pid_vnr(pid);

            if (clone_flags & CLONE_PARENT_SETTID)
                put_user(nr, parent_tidptr);

            //如果调用的 vfork()方法,初始化 vfork 完成处理信息。
            if (clone_flags & CLONE_VFORK) {
                p->vfork_done = &vfork;
                init_completion(&vfork);
                get_task_struct(p);
            }

            //将子进程加入到调度器中,为其分配 CPU,准备执行
            wake_up_new_task(p);

            //fork 完成,子进程即将开始运行
            if (unlikely(trace))
                ptrace_event_pid(trace, pid);

            //如果是 vfork,将父进程加入至等待队列,等待子进程完成
            if (clone_flags & CLONE_VFORK) {
                if (!wait_for_vfork_done(p, &vfork))
                    ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
            }

            put_pid(pid);
        } else {
            nr = PTR_ERR(p);
        }
        return nr;
}

fork, vfork, clone系统调用的贯彻


do_fork 流程

  • 调用 copy_process 为子进程复制出一份进度音信
  • 一旦是 vfork 伊始化落成管理音讯
  • 调用 wake_up_new_task 将子进程走入调解器,为之分配 CPU
  • 假即使 vfork,父进程等待子进度完结 exec 替换自身的地点空间

关于do_fork和_do_frok


The commit 3033f14ab78c32687 (“clone: support passing tls argument via
C
rather than pt_regs magic”) introduced _do_fork() that allowed to
pass
@tls parameter.

参见

linux2.5.32以后, 添加了TLS(Thread Local Storage)机制,
clone的标识CLONE_SETTLS接受三个参数来设置线程之处存款和储蓄区。sys_clone也由此扩充了多个int参数来传播相应的点tls_val。sys_clone通过do_fork来调用copy_process完成经过的复制,它调用特定的copy_thread和copy_thread把相应的种类调用参数从pt_regs存放器列表中领收取来,可是会促成意外的动静。

only one code path into copy_thread can pass the CLONE_SETTLS flag,
and
that code path comes from sys_clone with its architecture-specific
argument-passing order.

前方我们说了,
在落到实处函数调用的时候,作者iosys_clone等将一定连串布局的参数从贮存器中领到出来,
然后达到do_fork这步的时候曾经相应是系统构造无关了,
可是大家sys_clone须要安装的CLONE_SETTLS的tls仍然是个依赖与系统构造的参数,
这里就能现身难题。

因此linux-4.2之后选取引入叁个新的CONFIG_HAVE_COPY_THREAD_TLS,和一个新的COPY_THREAD_TLS接受TLS参数为
额外的长整型(系统调用参数大小)的纠纷。改动sys_clone的TLS参数unsigned
long,并传递到copy_thread_tls。

/* http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.5#L2646  */
extern long _do_fork(unsigned long, unsigned long, unsigned long, int __user *, int __user *, unsigned long);
extern long do_fork(unsigned long, unsigned long, unsigned long, int __user *, int __user *);


/* linux2.5.32以后, 添加了TLS(Thread Local Storage)机制, 
    在最新的linux-4.2中添加了对CLONE_SETTLS 的支持 
    底层的_do_fork实现了对其的支持, 
    dansh*/
#ifndef CONFIG_HAVE_COPY_THREAD_TLS
/* For compatibility with architectures that call do_fork directly rather than
 * using the syscall entry points below. */
long do_fork(unsigned long clone_flags,
              unsigned long stack_start,
              unsigned long stack_size,
              int __user *parent_tidptr,
              int __user *child_tidptr)
{
        return _do_fork(clone_flags, stack_start, stack_size,
                        parent_tidptr, child_tidptr, 0);
}
#endif

 

我们会发觉,新本子的类别中clone的TLS设置标志会通过TLS参数字传送递,
由此_do_fork代替了老版本的do_fork。

老版本的do_fork唯有在如下景况才会定义

  • 唯有当系统不援救通过TLS参数通过参数字传送递而是采取pt_regs寄放器列表传递时

  • 未定义CONFIG_HAVE_COPY_THREAD_TLS宏

参数 描述
clone_flags 与clone()参数flags相同, 用来控制进程复制过的一些属性信息, 描述你需要从父进程继承那些资源。该标志位的4个字节分为两部分。最低的一个字节为子进程结束时发送给父进程的信号代码,通常为SIGCHLD;剩余的三个字节则是各种clone标志的组合(本文所涉及的标志含义详见下表),也就是若干个标志之间的或运算。通过clone标志可以有选择的对父进程的资源进行复制;
stack_start 与clone()参数stack_start相同, 子进程用户态堆栈的地址
regs 是一个指向了寄存器集合的指针, 其中以原始形式, 保存了调用的参数, 该参数使用的数据类型是特定体系结构的struct pt_regs,其中按照系统调用执行时寄存器在内核栈上的存储顺序, 保存了所有的寄存器, 即指向内核态堆栈通用寄存器值的指针,通用寄存器的值是在从用户态切换到内核态时被保存到内核态堆栈中的(指向pt_regs结构体的指针。当系统发生系统调用,即用户进程从用户态切换到内核态时,该结构体保存通用寄存器中的值,并被存放于内核态的堆栈中)
stack_size 用户状态下栈的大小, 该参数通常是不必要的, 总被设置为0
parent_tidptr 与clone的ptid参数相同, 父进程在用户态下pid的地址,该参数在CLONE_PARENT_SETTID标志被设定时有意义
child_tidptr 与clone的ctid参数相同, 子进程在用户太下pid的地址,该参数在CLONE_CHILD_SETTID标志被设定时有意义

其中clone_flags如下表所示

新蒲京娱乐场777 2

copy_process 流程

追踪copy_process 代码(部分)

static 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)
{
    int retval;

    //创建进程描述符指针
    struct task_struct *p;

    //……

    //复制当前的 task_struct
    p = dup_task_struct(current);

    //……

    //初始化互斥变量  
    rt_mutex_init_task(p);

    //检查进程数是否超过限制,由操作系统定义
    if (atomic_read(&p->real_cred->user->processes) >=
            task_rlimit(p, RLIMIT_NPROC)) {
        if (p->real_cred->user != INIT_USER &&
            !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
            goto bad_fork_free;
    }

    //……

    //检查进程数是否超过 max_threads 由内存大小决定
    if (nr_threads >= max_threads)
        goto bad_fork_cleanup_count;

    //……

    //初始化自旋锁
    spin_lock_init(&p->alloc_lock);
    //初始化挂起信号
    init_sigpending(&p->pending);
    //初始化 CPU 定时器
    posix_cpu_timers_init(p);

    //……

    //初始化进程数据结构,并把进程状态设置为 TASK_RUNNING
    retval = sched_fork(clone_flags, p);

    //复制所有进程信息,包括文件系统、信号处理函数、信号、内存管理等
    if (retval)
        goto bad_fork_cleanup_policy;

    retval = perf_event_init_task(p);
    if (retval)
        goto bad_fork_cleanup_policy;
    retval = audit_alloc(p);
    if (retval)
        goto bad_fork_cleanup_perf;
    /* copy all the process information */
    shm_init_task(p);
    retval = copy_semundo(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_audit;
    retval = copy_files(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_semundo;
    retval = copy_fs(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_files;
    retval = copy_sighand(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_fs;
    retval = copy_signal(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_sighand;
    retval = copy_mm(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_signal;
    retval = copy_namespaces(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_mm;
    retval = copy_io(clone_flags, p);

    //初始化子进程内核栈
    retval = copy_thread(clone_flags, stack_start, stack_size, p);

    //为新进程分配新的 pid
    if (pid != &init_struct_pid) {
        retval = -ENOMEM;
        pid = alloc_pid(p->nsproxy->pid_ns_for_children);
        if (!pid)
            goto bad_fork_cleanup_io;
    }

    //设置子进程 pid  
    p->pid = pid_nr(pid);

    //……

    //返回结构体 p
    return p;
  • 调用 dup_task_struct 复制当前的 task_struct
  • 自己研商进程数是还是不是超越约束
  • 开头化自旋锁、挂起复信号、CPU 计时器等
  • 调用 sched_fork 初始化进度数据构造,并把进度景况设置为
    TASK_RUNNING
  • 复制全体进度新闻,满含文件系统、连续信号管理函数、能量信号、内部存款和储蓄器管理等
  • 调用 copy_thread 伊始化子进程内核栈
  • 为新历程分配并设置新的 pid

sys_fork的实现


不等种类布局下的fork达成sys_fork首假如因此申明集结区分,
在多数系统构造上, 规范的fork完成方式与如下

最早实现

架构 实现
arm arch/arm/kernel/sys_arm.c, line 239
i386 arch/i386/kernel/process.c, line 710
x86_64 arch/x86_64/kernel/process.c, line 706
asmlinkage long sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.rsp, &regs, 0);
}

 

新版本

#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
        return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
        /* can not support in nommu mode */
        return -EINVAL;
#endif
}
#endif

 

小编们能够见见独一利用的注脚是SIGCHLD。那意味着在子进度终止后将发送非随机信号SIGCHLD实信号布告父进度,

由于写时复制(COWState of Qatar能力, 最先父子过程的栈地址相仿,
然而一旦操作栈地址闭并写入数据,
则COW机制会为每一种进度分别创设五个新的栈别本

如果do_fork成功, 则新建进度的pid作为系统调用的结果再次回到, 不然赶回错误码

dup_task_struct 流程

static struct task_struct *dup_task_struct(struct task_struct *orig)
{
    struct task_struct *tsk;
    struct thread_info *ti;
    int node = tsk_fork_get_node(orig);
    int err;

    //分配一个 task_struct 节点
    tsk = alloc_task_struct_node(node);
    if (!tsk)
        return NULL;

    //分配一个 thread_info 节点,包含进程的内核栈,ti 为栈底
    ti = alloc_thread_info_node(tsk, node);
    if (!ti)
        goto free_tsk;

    //将栈底的值赋给新节点的栈
    tsk->stack = ti;

    //……

    return tsk;

}

调用alloc_task_struct_node分配三个 task_struct 节点

调用alloc_thread_info_node分配几个 thread_info
节点,其实是分配了二个thread_union联合体,将栈底再次来到给 ti

union thread_union {
   struct thread_info thread_info;
  unsigned long stack[THREAD_SIZE/sizeof(long)];
};

最后将栈底的值 ti 赋值给新节点的栈

末段实践完dup_task_struct之后,子进度除了tsk->stack指针不一样之外,全体都肖似!

sys_vfork的实现


前期完结

架构 实现
arm arch/arm/kernel/sys_arm.c, line 254
i386 arch/i386/kernel/process.c, line 737
x86_64 arch/x86_64/kernel/process.c, line 728
asmlinkage long sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.rsp, &regs, 0);
}

 

新版本

#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{
        return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
                        0, NULL, NULL, 0);
}
#endif

 

能够观察sys_vfork的贯彻与sys_fork只是轻微不一样,
前边多少个使用了额外的标记CLONE_VFORK | CLONE_VM

sched_fork 流程

core.c

int sched_fork(unsigned long clone_flags, struct task_struct *p)
{
    unsigned long flags;
    int cpu = get_cpu();

    __sched_fork(clone_flags, p);

    //将子进程状态设置为 TASK_RUNNING
    p->state = TASK_RUNNING;

    //……

    //为子进程分配 CPU
    set_task_cpu(p, cpu);

    put_cpu();
    return 0;
}

大家得以寓目sched_fork大约造成了两项根本工作,一是将子进程情况设置为
TASK_RUNNING,二是为其分配 CPU

sys_clone的实现


早先时期完成

架构 实现
arm arch/arm/kernel/sys_arm.c, line 247
i386 arch/i386/kernel/process.c, line 715
x86_64 arch/x86_64/kernel/process.c, line 711

sys_clone的实现方式与上述系统调用类似, 但实际差别在于do_fork如下调用

casmlinkage int sys_clone(struct pt_regs regs)
{
    /* 注释中是i385下增加的代码, 其他体系结构无此定义
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;*/
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}

 

新版本

#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
                 int __user *, parent_tidptr,
                 unsigned long, tls,
                 int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
                 int __user *, parent_tidptr,
                 int __user *, child_tidptr,
                 unsigned long, tls)
#elif defined(CONFIG_CLONE_BACKWARDS3)
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
                int, stack_size,
                int __user *, parent_tidptr,
                int __user *, child_tidptr,
                unsigned long, tls)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
                 int __user *, parent_tidptr,
                 int __user *, child_tidptr,
                 unsigned long, tls)
#endif
{
        return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
}
#endif

 

我们得以看出sys_clone的标记不再是硬编码的,
而是通过逐条寄放器参数字传送递到系统调用, 因此大家需求领取那么些参数。

除此以外,clone也不再复制进程的栈, 而是能够钦点新的栈地址, 在生成线程时,
恐怕须求这么做, 线程大概与父进程分享地址空间,
可是线程本人的栈恐怕在此外贰个地点空间

其余还吩咐了客商空间的四个指针(parent_tidptr和child_tidptr卡塔尔(قطر‎,
用于与线程库通讯

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图