标记进程
- PID: 进程/线程ID
一个唯一的进程标识符(PID)来标识进程,PID存放在进程描述符的pid字段中。PID顺序编号,新创建进程的PID通常是上一个进程PID+1
- TGID:进程ID/线程组ID
一个进程中的所有线程共享相同的tgid。进程的PID和tgid相同
- pgid:进程组ID
每个进程属于一个进程组,每个进程组有一个Leader进程,也就是进程ID等于进程组ID的那个进程。进程组有生命周期,它的生命周期开始于进程组leader创建进程组,结束于进程组内的最后一个进程离开进程组
进程状态
进程在某一时刻只可能有一种状态。进程可能的状态有:
- 可运行状态(TASK_RUNNING): 进程正在执行或者就绪等待CPU调度执行。
- 可中断的等待状态(TASK_INTERRUPTIBLE):进程被挂起(睡眠),直到某个条件变为真。处于等待队伍中,等待资源有效时唤醒(比如等待键盘输入、socket连接、信号等等),但可以被中断唤醒。
- 不可中断的等待状态(TASK_UNINTERRUPTIBLE):与可中断的等待状态类似,此进程也在睡眠,但不会响应中断。在进程中断可能会导致意外设备状态的情况下使用。
- 暂停状态(TASK_STOPPED):进程的执行被暂停。当进程接收到SIGSTOP、SIGTSTP、SIGTTIN以及SIGTTOU信号后,进入暂停状态。
- 跟踪状态(TASK_TRACED):被其他进程跟踪,比如通过ptrace对调试程序进行跟踪。
- 僵死状态(EXIT_ZOMBIE):子进程资源释放但是进程表项未销毁。
- 僵死撤销状态(EXIT_DEAD):当父进程清理(获取)剩余的子进程结构时,进程现在已彻底释放。此状态从不会在进程列出实用程序中看到。.
进程/线程创建
可以使用fork或vfork创建进程,使用clone系统调用创建线程。在内部都是用kernel_clone实现。区别只是传入的args不同(用来控制新进程或线程与父进程或线程共享什么资源)。
- fork: 创造的子进程是父进程的完整副本,复制了父亲进程的资源
-
- 为什么fork有两个返回值?
因为这两个返回值是由不同的进程return出来的,而不是由一个fork函数返回两个数。(fork后,进程由一个变成两个,两个进程分别有一个返回值)
返回值:
- 成功创建一个子进程,对于父进程来说返回子进程ID
- 成功创建一个子进程,对于子进程来说返回值为0
- 如果为-1表示创建失败 - 子进程创建成功后,代码的执行的开始位置?
fork代码段的位置 - 父子进程的执行顺序?
不一定谁先谁后(看谁抢到CPU资源)
- 为什么fork有两个返回值?
- vfork: 子进程与父进程共享地址空间, 保证子进程先运行,在它调用 exec/exit之后,父进程才执行
进程拷贝或创建的一个整体调用流程:首先通过系统调用陷入内核,然后kernel_clone完成创建。其中描述符和资源的拷贝,pid的分配等都在copy_process中完成,完成创建后需要通过wake_up_new_task加入调度
kernel_clone
- 首先是进行copy_process,copy_process调用 dup task struct()为新进程创建一个内核栈、thread info 结构和 task struct,这些值与当前进程的值相同。此时,子进程和父进程的描述符是完全相同的。
- 通过copy_creds复制父进程的权限和身份信息。这之后去检查新创建这个子进程后,当前用户所拥有的进程数目没有超出给分配的资源的限制。
- 然后是sched_fork,这个函数进行调度相关的初始化比如设置优先级,调度类,就绪队列等,把进程设置为TASK_NEW,无法运行也无法唤醒(毕竟这时候还没on_rq),这个函数在后面将进程调度会分析。
- 然后就是各种资源的copy了,copy之后就分配全局pid和tgid。
- copy_process结束,返回一个指子进程描述符的指针
- wake_up_new_task使新进程进入就绪状态,并加入就绪队列中,然后检查新进程是否满足抢占当前正在运行进程的条件,如果满足抢占条件需要设置TIF_NEED_RESCHED标志位。
- 如果是vfork会将父进程阻塞,直到子进程调用exec/exit之后,父进程才执行