多进程

一,进程相关的概念

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会回收。不过子进程回收还要自己做。

 

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值