一,进程相关的概念
1,程序与进程
程序:是编译好的二进制文件,在磁盘上。不占用系统资源(cpu、内存、打开的文件、设备、锁....)
进程:进程是活跃的程序,占用系统资源。
2,并发
在操作系统中,一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态。但同一时刻只能运行一个进程。在这个时间段中,cpu给每一个进程,分配一个时间轮片。
3,进程地址空间与MMU
CPU:预取器,译码器,算逻单元,寄存器堆,MMU。。。。
MMU在CPU内作用有两点:
- 使虚拟内存映射到物理内存
- 设置修改内存访问级别
4,进程控制块(PCB)
linux内核的进程控制块是task_struct结构体。
/usr/src/linux-headers-3.16.0-30/include/linux/sched.h文件中可以查看struct task_struct 结构体定义
部分成员:
* 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
* 进程的状态,有就绪、运行、挂起、停止等状态。
* 进程切换时需要保存和恢复的一些CPU寄存器。
* 描述虚拟地址空间的信息。
* 描述控制终端的信息。
* 当前工作目录(Current Working Directory)。
* umask掩码。
* 文件描述符表,包含很多指向file结构体的指针。
* 和信号相关的信息。阻塞信号集(信号屏蔽字),未决信号集。
* 用户id和组id。
* 会话(Session)和进程组。
* 进程可以使用的资源上限(Resource Limit)。
5,进程状态
有三态模型和五态模型
就绪态:当进程已分配到除CPU以外所有必要的资源,只要获取CPU就可以执行
执行态:占用CPU,正在执行
阻塞态:正在执行过程中,由于等待某个事件而无法执行,等待除CPU以外的资源而阻塞,此时放弃CPU。
新建态和终止态
6,常见环境变量
环境变量:是指在操作系统中用来指定操作系统运行环境的一些参数。每个进程有一套不同的环境变量。
PATH:可执行文件的搜索路径
SHELL:当前shell类型
HOME:当前用户主目录的路径
TERM(term):当前终端类型,在图形界面终端下它的值通常是xterm
LANG(lang):语言和locale,决定了字符编码以及时间、货币等信息的显示格式
1,getenv函数:获取环境变量值
char *getenv(const char *name); 成功:返回环境变量的值;失败:NULL (name不存在)
2,setenv函数:设置环境变量的值
int setenv(const char *name, const char *value, int overwrite); 成功:0;失败:-1
参数overwrite取值: 1:覆盖原环境变量
0:不覆盖。(该参数常用于设置新环境变量,如:ABC = haha-day-night)
3,unsetenv函数:删除环境变量name的定义
int unsetenv(const char *name); 成功:0;失败:-1
注意事项:name不存在仍返回0(成功),当name命名为"ABC="时则会出错。
二,进程控制原语
1,fork函数
#include <unistd.h>
pid_t fork(void);
pid_t :有符号整数
返回值:-1 失败 返回成功:父进程返回子进程PID,子进程返回0。
注意并不是fork函数有两个返回值,而是fork后变为两个。
功能:创建一个子进程
如果想用for循环创建n个子进程,这样会创建出2^n -1子进程。除非在子进程中break
2,getpid函数
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
功能:获取当前进程ID
3,getppid函数
#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
功能:获取当前进程的父进程ID
4,getuid函数
#include <unistd.h>
#include <sys/types.h>
uid_t getuid(void);
功能:获取当前进程实际用户ID
5,geteuid函数
#include <unistd.h>
#include <sys/types.h>
uid_t geteuid(void);
功能:获取当前进程有效用户ID
6,getgid函数
#include <unistd.h>
#include <sys/types.h>
gid_t getgid(void);
功能:获取当前进程使用用户组ID
7,getegid函数
#include <unistd.h>
#include <sys/types.h>
gid_t getegid(void);
获取当前进程有效用户组ID
8,wait函数
#include <sys/wait.h>
pid_t wait(int *stat_loc);
stat_loc:进程退出状态,传出参数
返回值:成功是子进程ID,失败是-1(没有子进程)
功能: ① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
1. WIFEXITED(status) 为非0 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
2. WIFSIGNALED(status) 为非0 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
*3. WIFSTOPPED(status) 为非0 → 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
用这些宏函数来获取退出状态值(正常时return exit指定的值,异常时是一些信号值
9,waitpid函数
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *stat_loc, int options);
pid:> 0 回收指定ID的子进程
-1 回收任意子进程(相当于wait)waitpid(-1,NULL,0)==wait(NULL)
0 回收和当前调用waitpid一个组的所有子进程
-1 回收指定进程组内的任意子进程
options:为0时,是阻塞模式,为WNOHANG是非阻塞
返回值:成功是子进程ID,失败是-1(没有子进程)当options参数是WNOHANG时,返回0表示要等待的子进程在运行中。
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
三,进程共享
刚fork之后:
父子相同处: 全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式...
父子不同处: 1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集7,父进程的锁
似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但pid不同。但是 现在实际并不是这样,父子进程遵循读时共享,写时复制(针对物理地址来说)。写时才复制物理空间
父子进程一定共享的是:1. 文件描述符
2. mmap建立的映射区
GDB调试:默认是跟踪父进程
set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程。
set follow-fork-mode parent 设置跟踪父进程。
注意,一定要在fork函数调用之前设置才有效。
四,exec函数族
调用exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。但并没有创建新的进程,进程ID没变
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
成功无返回,失败返回-1
参数都要以NULL结束。
execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在PATH中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL); 使用参数1给出的绝对路径搜索。
记忆:
l (list) 命令行参数列表
p (path) 搜素file时使用path变量
v (vector) 使用命令行参数数组
e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。
execve只有它是系统调用。
五,孤儿进程与僵尸进程
孤儿进程:父进程先于子进程结束,此时子进程称为孤儿进程。但init进程会成为子进程的父进程。并为他收尸
僵尸进程:子进程进程终止,父进程尚未回收(在循环),子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。父进程天生就要为子进程收尸,子进程PCB中信息决定的。
当子进程终止时,内核给父进程产生一个SIGCHID的信号,不过父进程没有捕获此信号,而该信号的默认行为就是忽略。
怎么回收僵尸进程?
- 父进程通过调用wait或waitpid等待子进程结束并回收残余资源
- 父进程很忙,可以扑捉SIG_CHLD信号,在扑捉函数中调用wait函数
- 如果父进程不关心子进程什么时候结束,那么可以用signal(SIG_CHLD,SIG_IGN)通知内核,自己对子进程结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号
- fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出。那么孙进程是孤儿进程,会被init接管,孙进程结束后,init会回收。不过子进程回收还要自己做。