Linux进程创建

进程描述符

进程描述符(process descriptor)包含了一个具体进程的所有信息。他的结构体定义在中,代码见linux/sched.h,类型为task_struct。进城描述符几乎包含了所有进程相关的上下文,下面简单列举了一些:

  • state: 描述了进程的当前状态信息(runable=0, unrunable=-1, stopped>0)
  • pid: 唯一的进程标志
  • cpu / recent_used_cpu等cpu相关信息
  • mm等内存相关信息
  • exitcode / exitstate等进程退出相关信息,通常由wait4()接收
  • 调度相关信息
  • parent / sibling: 父进程和兄弟进程等信息
  • fs / files / nsproxy: 文件系统信息,打开的文件描述符以及命名空间等信息
  • signal / signalhand: 信号量以及处理函数等信息
  • irq: 软/硬中断请求信息
  • bio_list / journal_info: 块设备信息,文件系统日志信息等IO相关的上下文
  • cgroups: 控制组相关信息
  • thread: 线程相关上下文

进程描述符的存放

在kernel<2.6中,进程的task_struct存放在各自的内核栈的尾部,不过在后来的内核中,由于使用了slab分配器来动态生成task_struct,所以还需要在栈尾部的thread_info中存放task_struct的指针。

在寄存器富裕的硬件体系架构,可能会有一个专门的寄存器用来存放当前进程task_struct的指针,因为在内核的进程处理相关逻辑中,这个结构体经常被用到。不过在x86这种寄存器不富裕的架构,当通过current宏返回进程描述符时,就需要通过内核栈尾部的thread_info寻找task_struct的位置了。

state 进程状态

当内核需要调整某个进程的状态时,可以使用set_task_state(task, state),可以调整到如下五种状态:

  • runable=0
    • TASK_RUNNING: 进程处于可执行状态,它要么在执行队列中等待被执行,要么正在被执行
  • unrunable>0
    • TASK_INTERRUPTIBLE: 进程处于阻塞状态,等待某些条件达成。当收到信号的时候,进程会被提前唤醒
    • TASK_UNINTERRUPTIBLE: 进程处于阻塞状态,等待某些条件达成。这个状态下会忽略信号,不可中断。
    • __TASK_TRACED: 进程正在被其他进程跟踪,通常用于ptrace等调试工具调试
  • stopped=-1
    • __TASK_STOPPED: 进程停止运行

进程创建

当调用fork创建子进程时,Linux会通过拷贝当前进程来创建子进程。由于使用了写时拷贝页技术(CopyOnWrite),所以进程的创建只需要复制页表和创建进程描述符,速度是比较快的。

进程拷贝调用链为fork -> clone -> do_fork(kernel/fork.c) -> copy_process,其中copy_process

  1. 首先创建一个内核栈,并拷贝父进程的thread_info以及进程描述符到内核栈中
  2. 将进程描述符中非继承的统计量设初始值,并将状态置为TASK_UNINTERRUPTIABLE,保证进程不会运行
  3. 分配一个有效的PID
  4. 拷贝或共享打开的文件句柄,文件系统信息,信号处理函数,进城地址空间和命名空间等。
  5. 扫尾工作并返回子进程指针
线程仅仅被视为与其他进程共享某些资源的进程

进程终结

当进程显式调用exit()或者main()函数返回隐式调用exit,或者接收到无法处理/忽略的信号量或异常,就得调用do_exit来终结这个进程:

  1. 状态转换,将task_struct中的flag设置为PF_EXITING
  2. 清理/释放IPC信号、地址空间、定时器、文件引用计数等系统资源
  3. 在进程描述符中设置exitcode,并设置exit_state为EXIT_ZOMBIE
  4. 最后调用schedule()切换到执行新的进程,该进程结束
    不过还剩下了task_struct以及内核栈中的数据没有被释放,他们的作用就是通知父进程,子进程的结束状态。父进程将会收到一个信号量,此时可以通过wait来获取exitcode以及删除子进程的参与信息(内核栈,进程描述符,线程信息)

孤儿进程

当一个父进程退出时,就需要为他的子进程重新设置父进程,来接收未来的exitcode。首先会尝试在当前进程组中寻找一个进程作为父亲,如果找不到,就让init进程作为父进程。

你可能感兴趣的:(Linux进程创建)