从源码入手,详解Linux进程
概念
进程 = PCB + 对应的代码和数据
PCB:Process Control Block【进程控制块】
在Linux中是:task_struct,本质是结构体组成的链表
PCB与对应的代码和数据都存储在内存中,但是CPU会通过OS直接操作PCB,间接控制上层代码/数据的运行及处理等。由于PCB的本质是链表,因此OS控制进程的方式实际是对链表进行增、删、查、改操作。
Task_struct
C语言中,结构体是由一个或多个成员变量组成的自定义类型。换句话说,这些成员变量是结构体所具有的不同属性。
Linux内核中的PCB实际就是一个结构体,被成为Task_struct。Task_struct中的成员变量即为线程所具有的属性。
以下摘自0.11版本Linux内核源码
// 任务(进程)数据结构struct task_struct {/* these are hardcoded - don't touch */long state; /* -1 unrunnable, 0 runnable, >0 stopped */ // 任务的运行状态(-1 不可运行,0 可运行(就绪),>0 已停止)long counter; //counter 值的计算方式为 counter = counter /2 + priority, 优先执行counter最大的任务; 任务运行时间计数(递减)(滴答数)(时间片)long priority; // 运行优先数。任务开始运行时 counter = priority,越大运行越长long signal; // 信号。是位图,每个比特位代表一种信号,信号值=位偏移值+1struct sigaction sigaction[32]; // 信号执行属性结构,对应信号将要执行的操作和标志信息long blocked; /* bitmap of masked signals */ // 进程信号屏蔽码(对应信号位图)
/* various fields */int exit_code; //任务执行停止的退出码,其父进程会取unsigned long start_code,end_code,end_data,brk,start_stack; // 代码段地址,代码长度,数据长度,总长度,栈段地址long pid,father,pgrp,session,leader; // 进程标识号,父进程号,父进程组号,会话号,会话首领unsigned short uid,euid,suid; // 用户标识号, 有效用户id,保存的用户idunsigned short gid,egid,sgid; // 组标识号,有效组号,保存的组号long alarm; // 报警定时器值long utime,stime,cutime,cstime,start_time; // 用户态运行时间,系统态运行时间,子进程用户态运行时间,子进程系统态运行时间,进程开始运行时刻unsigned short used_math; // 是否使用了协处理器
/* file system info */int tty; /* -1 if no tty, so it must be signed */ // 进程使用 tty 的子设备号。-1 表示没有使用unsigned short umask; // 文件创建属性屏蔽位struct m_inode * pwd; // 当前工作目录i节点struct m_inode * root; // 跟目录i节点struct m_inode * executable; // 执行文件i节点unsigned long close_on_exec; // 执行时关闭文件句柄位图标志struct file * filp[NR_OPEN]; // 进程使用的文件表结构,用于保存文件句柄
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */struct desc_struct ldt[3]; // 局部描述符段, 0-空,1-代码段 cs,2-数据和堆栈段 ds&ss
/* tss for this task */struct tss_struct tss; // 本进程的任务状态段信息结构
};
比较重要的变量:
- 与信号相关的两个变量
signal
和blocked
,即分别为信号待决表,信号屏蔽表。 exit_code
进程退出start_code
,end_code
,end_data
,brk
,start_stack
为虚拟地址空间不同段的起始标识。pid
进程号umask
文件创建时的权限屏蔽字
对进程进行操作
在C语言中,用户可以调用对应的函数对进程进行操作。
进程有其自己的类型pid_t
,大部分与相关的函数的返回值类型都是pid_t
类型。
getpid() & getppid()
调用这两个函数分别可以获得当前进程的id,及其父进程的id.
使用时需要包含头文件
#include <sys/types.h>
#include <unistd.h>
getpid() returns the process ID (PID) of the calling process. (This is often used by routines that generate unique temporary filenames.)
getppid() returns the process ID of the parent of the calling process. This will be either the ID of the process that created this process using fork(), or, if that process has already terminated, the ID of the process to which this process has been reparented (either init(1) or a “subreaper” process defined via the prctl(2) PR_SET_CHILD_SUBREAPER operation).
wait() & waitpid()
二者都是系统调用,父进程使用,目的是等待子进程运行完毕,接收返回信息。
在多进程任务中,等待子进程调用结束是必须的,因为需要防止僵尸进程的出现,父进程需要及时回收子进程开辟的空间。详见工作手册↓
$ man wait
wait()
函数原型:: pid_t wait(int *wstatus);
wait()与waitpid(),二者略有不同,简单来说,wait()的功能简单直接:等待当前父进程创建的所有子进程,直到其中一个子进程退出为止。在实际运用中不常使用。
返回值:: 成功等待则返回结束子进程的id,否则返回-1.
RETURN VALUE
wait(): on success, returns the process ID of the terminated child; on error, -1 is returned.waitpid(): on success, returns the process ID of the child whose state has changed; if WNOHANG was specified and one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is returned. On error, -1 is returned.
waitpid()
函数原型:: pid_t waitpid(pid_t pid, int *wstatus, int options);
参数pid的取值范围为[-inf, inf],具体有不同的含义:
< -1 meaning wait for any child process whose process group ID is equal to the absolute value of pid.
-1 meaning wait for any child process.
0 meaning wait for any child process whose process group ID is equal to that of the calling process at the time of the call to waitpid().
>0 meaning wait for the child whose process ID is equal to the value of pid.
当参数选择<-1
时,等待进程组ID为参数绝对值的任一子进程。
当参数选择-1
时,其效果与wait()相同。
当参数选择0
时,等待与父进程所在进程组的任一子进程。
当参数选择>0
时,则相当于指定等待该进程号的进程。
若指定等待成功则返回0,否则返回-1.
如果想实际进行进程调度的实操,可以参阅另一篇博客从零开始实现Shell | Linux进程调度实战