进程管理
一、进程的基本概念
1、进程与程序
程序是存储在磁盘上的可执行文件,程序被加载到内存中开始运行称为进程。一个程序可以同时加载多个进程,
进程就是处于活动状态下的程序
2、进程的分类
进程根据功能不同分为三种类型:交互进程、批处理进程、守护进程
交互进程:由一个shell终端启动的进程,在运行过程中需要与用户进行交互操作,可以运行于前台,也可以在
后台运行
批处理进程:该进程是一个进程的合集,负责按顺序执行预定义好的操作
守护进程:一般处于活跃状态,运行在后台,一般都是由系统在开机时通过启动系统脚本自动创建
3、查看进程
简单形式:ps 显示当前用户有控制终端的进程简单信息
列表形式:ps -auxw 以最大列宽显示所有进程的详细信息
USER 进程的属主
PID 进程的编号(从1开始,每个都不相同)
%CPU CPU的使用率
%MEM 内存的使用率
VSZ 虚拟内存使用的字节数
RSS 物理内存使用的字节数
TTY 终端设备号 ?表示的是无终端控制
STAT 进程状态
O 就绪态 等待被调度
R 运行态 Linux没有O,就绪也用R表示
S 可被唤醒的休眠态,例如系统中断、获得资源时候、收到信号都可以唤醒进入R
D 不可被唤醒的睡眠态,只能被系统唤醒
T 暂停态 收到信号SIGSTOP转入T,收到SIGCONT转入R
Z 僵尸态
< 高优先级
N 低优先级
s 进程组中的领导者
l 多线程的进程
+ 处于后台的进程组
START TIME 进程运行的总时间
COMMAND 进程的启动命令
4、父进程、子进程、孤儿进程、僵尸进程
一个进程可以被另一个进程创建,创建者就称为被创建者的父进程,被创建者称为创建者的子进程,
当子进程被父进程创建启动后动后在操作系统的调度下同时进行
当子进程先于父进程结束时,子进程会向父进程发送SIGCHLD信号,此时父进程应该去回收子进程
的相关资源,如果父进程没有去回收子进程,那么子进程变成僵尸进程
父进程先于子进程结束,子进程就变成孤儿进程,所有的孤儿进程都会被系统指定的一个进程领养
接管,该进程就变成了孤儿进程的父进程
5、进程id号(进程标识符)
每个进程都有一个以非负整数表示的唯一标识,即进程ID\PID
进程ID在任意时刻中都是唯一的,但是可以重用,进程一旦结束
它的进程ID会被系统回收,过一段时间后,可以继续分配给新的
进程(延时重用)
pid_t getpid(void);
功能:获取调用者的进程ID
pid_t getppid(void);
功能:获取调用者父进程的进程ID
二、进程的创建
pid_t fork(void);
功能:创建一个子进程
返回值:成功时一次调用两次返回,子进程返回0,父进程返回的是子进程的pid
当进程的数量超过系统的限制可能会创建失败返回一次,返回的是-1
注意:
1、子进程创建成功之后,会与父进程同样从fork处开始继续往下执行,为了让父子进程分别执行不同的功能,可以通过返回值的不同
让它们进入不同的分支语句
2、通过fork创建的子进程会拷贝父进程的数据段、bss段、堆、栈、I/O流缓冲区等数据,与父进程共享代码段段数据,子进程会继承
父进程的信号处理方式
3、fork结束后,不确定是父进程先返回还是子进程先返回,可以通过休眠函数来让另一个进程先执行(也就是sleep)
4、通过fork创建的子进程可以共享父进程的文件描述符
练习:通过fork创建四个子进程,再为这四个子进程各自创建两个子进程
pid_t vfork(void);
功能:以加载可执行程序的方式创建并运行子进程
返回值:成功子进程返回0 父进程返回子进程pid
注意:
1、子进程一定先返回,但此时子进程不算完全创建成功,需要加载一个可执行程序来替换当前子进程所有资源,替换完成之后,子进程才算
完全创建成功,此时父进程才会返回
使用exec系列函数让子进程加载可执行文件
int execl(const char *pathname, const char *arg, ...
/* (char *) NULL */);
path:要加载的可执行文件的路径
arg:命令行参数 最后一个一定要以NULL结尾
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
file:要加载的可执行文件的名字,会根据PATH环境变量中记录的路径
查找并加载该文件
注意:如果想要指定一个路径,那么需要在配置文件中增加PATH路径
int execle(const char *pathname, const char *arg, ...
/*, (char *) NULL, char *const envp[] */);
path:要加载的可执行文件的路径
arg:命令行参数 最后一个一定要以NULL结尾
envp:环境变量表,
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
注意:
1、exec系列函数成功不返回返回值,失败才会返回-1
2、以vfork、exec函数创建出来的子进程不会继承父进程的信号处理方式,
但是能继承父进程的信号屏蔽集
考点:对比fork和vfork区别?
int system(const char *command);
功能:创建子进程加载command程序来执行系统命令
三、进程的正常退出
1、执行了main函数的return n语句,该返回值可以被父进程接收到
2、进程执行调用了exit函数,该函数是属于C标准库提供的
void exit(int status);
功能:能在任意时候结束进程
status:结束状态码EXIT_SUCCESS\EXIT_FAILURE,效果与main函数return 的值等同
注意:该函数不会返回
int atexit(void (*function)(void));
功能:向内核注册绑定一个进程结束前要执行的函数
int on_exit(void (*function)(int , void *), void *arg);
功能:向内核注册绑定一个进程结束前要执行的函数
arg:会在调用function的时候传递给该函数
进程正常退出前要完成步骤:
注意:
1、先调用atexit\on_exit函数进行注册,如果注册多个函数,结束时这些函数的执行顺序与
注册顺序相反
2、冲刷并关闭所有打开状态下的标准IO流
3、调用_exit\_Exit函数
3、调用_exit\_Exit函数
void _exit(int status);
功能:结束进程,由系统提供
void _Exit(int status);
功能:结束进程,由标准库提供
1、它们的参数status会被父进程获取到
2、结束前会关闭所有打开状态下的系统IO的文件描述符
3、向父进程发出结束信号SIGCHLD
4、该函数不会返回
4、进程的最后一个线程执行了return返回语句
5、进程的最后一个线程执行了pthread_exit函数
四、进程的异常终止
1、进程调用了abort函数,产生了SIGABRT信号
2、进程接收到了其他进程的某些信号,导致终止
3、进程自己的错误操作导致终止,例如:段错误、除零等
4、进程的最后一个线程接收到了“取消”请求操作,并相应了请求
注意:以上的异常终止方式,都会让父进程无法获取结束进程的结束
状态码,所以才叫异常终止
注意:无论进程是如何结束的,他们的资源都会被回收,所有的
文件描述符、文件指针都会被关闭
五、子进程的回收
对于任何结束方式,都希望父进程能够获取到,通过wait、waitpid函数可以知道子进程是如何结束的以及结束状态码
pid_t wait(int *status);
功能:等待任意子进程的结束,并获取其结束状态码
status:输出型参数
返回值:子进程的ID
1、如果所有子进程都还运行,则阻塞等待
2、如果有一个子进程结束,立即返回该进程的结束状态码和pid
3、如果没有子进程运行,立即返回-1
WIFEXITED(status)
判断子进程是否是正常退出,如果是返回真
WEXITSTATUS(status)
如果子进程是正常退出的,可以获取正确的结束状态码,只获取低八位
WIFSIGNALED(status)
判断子进程是否是异常退出,如果是返回真
WTERMSIG(status)
如果子进程是异常退出的,可以获取杀该子进程的信号id
pid_t waitpid(pid_t pid, int *status, int options);
功能:指定回收某个或者某些子进程的状态
pid:
<-1 等待组id为abs(pid)的进程组中的任意子进程结束
-1 等待任意子进程结束 功能与wait等同
0 等待调用者同组的任意子进程结束
>0 等待该进程结束
status:处理与wait等同
options:等待模式
0 正常的阻塞模式 等同wait
WNOHANG 非阻塞模式 如果没有子进程结束,立即返回0
WUNTRACED 如果等待的子进程中有处于暂停态的,立即返回该子进程的状态码
WCONTINUED 如果等待的子进程中有从暂停态转入为运行的,立即返回该子进程的状态码
用于判断和获取status的宏函数:
WIFSTOPPED 判断状态码,如果子进程是转入暂停态,返回真
WSTOPSIG 当子进程是暂停态返回时,获取导致暂停态的信号id
WIFCONTINUED 判断状态码,如果子进程是由暂停态转入运行态,返回真
注意:
1、如果没有子进程结束 wait会阻塞等待,如果在父进程的业务逻辑代码出wait,会对父进程的业务产生阻塞影响,因为子进程结束时会给父进程发送SIGCHLD信号,可以通过在父进程中注册该信号处理函数,在该函数内执行wait,就不影响父进程的业务执行,但是信号最好选择可靠信号
2、waitpid可以选择不阻塞等待,因此不需要像wait一样在信号处理函数中执行,可以直接在父进程的业务逻辑中调用不受影响
进程间通信:
一、基本概念
什么是进程间通信:
是指两个或多个进程之间需要协同工作、交互数据的过程,因为进程之间是相互独立工作的,为了协同工作就需要进行通信来交互数据
进程间通信的分类:
简单的进程间通信:
信号(携带附加信息)、文件、环境变量、命令行参数等
传统的进程间通信:
管道文件(有名管道、匿名管道)
XSI的进程间通信:
共享内存、消息队列、信号量
网络的进程间通信:
socket套接字
二、传统的进程间通信技术-管道
管道是UNIX系统中最古老的进程间通信方式,古老就意味着所有的系统都支持,早期的管道是半双工,现在有些系统的管道是全双工(假定以半双工来编写代码)
管道是一种特殊的文件,它的数据在管道文件中是流动的,读取后就会自动消失,如果文件中没有数据要读取会阻塞等待
有名管道:
基于有文件名的管道文件的进程间通信,可以是任何进程之间的通信
编程模型:
进程A 进程B
创建管道 ...
打开管道 打开管道
写数据 读数据
关闭管道 关闭管道
删除管道 ...
创建有名管道文件
命令: mkfifo filename
函数:int mkfifo(const char *pathname, mode_t mode);
功能:创建有名管道文件
pathname:文件的路径
mode:文件的权限掩码
匿名管道:没有名字的管道文件
只适合通过fork创建的有血缘关系的进程间通信(父子进程、兄弟进程之间)
int pipe(int pipefd[2]);
功能:创建并打开一个匿名管道文件
pipefd:输出型参数 返回该匿名管道文件的读权限fd和写权限fd
pipefd[0] 用于读管道文件
pipefd[1] 用于写管道文件
编程模型:
父进程 子进程
获取一对fd ...
创建子进程 共享一对fd
关闭读fd[0] 关闭写fd[1]
fd[1]写数据 fd[0]读数据
关闭fd[1] 关闭fd[0]
三、XSI进程间通信
XSI是X/open 公司制定的用于进程间通信的系统(S)接口(I)标准
XSI进程间通信标准都需要借助系统内核完成,需要创建内核对象,内核对象以整数形式返回给用户,相当于文件描述符\文件指针,代表了某个内核对象完成某次进程间通信任务,也叫做IPC标识符
类似文件的创建需要借助文件名一样,IPC标识符的创建也需要借助IPC键值(整数),也跟文件名一样,要确保IPC键值是独一无二的
一般是通过函数生成一个独一无二的IPC键值
key_t ftok(const char *pathname, int proj_id);
功能:生成一个独一无二的IPC键值
pathname:项目路径
proj_id:项目编号
返回值:根据项目路径+项目编号自动计算出IPC键值
注意:计算IPC键值的方式不是根据pathname的字符串内容,而是依靠路径的位置,如果提供了假的路径,不管编号如何,都会得到相同的IPC键值,不正确
共享内存:
基本特点:
两个或多个进程共享同一块由内核负责维护的物理内存,该物理内存是需要与进程的虚拟内存进行映射后使用
优点:不需要进行磁盘读写操作,无需复制操作,是最快的一种IPC机制
缺点:需要考虑通信的同步问题,一般采用信号解决
int shmget(key_t key, size_t size, int shmflg);
功能:创建\获取共享内存
key:IPC键值
size:共享内存的大小,当是获取时此参数无意义写0即可
shmflg:
IPC_CREAT 创建共享内存,已存在时会直接获取
IPC_EXCL 配合CREAT,如果已存在则返回错误
获取时直接给0即可
注意:如果是创建IPC_CREAT,需要在后面额外按位或这段共享内存的读写权限IPC_CREAT|0644
返回值:成功返回该共享内存的IPC标识符,失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:虚拟内存与物理共享内存映射
shmid:IPC标识符
shmaddr:想要映射的虚拟内存首地址,一般给NULL就会自动安排
shmflg:
SHM_RND 只有当shmaddr不是NULL时才有效,当映射的字节数不足一页的整数倍时,会向下取整数倍的页数来映射
SHM_RDONLY 以只读方式映射共享内存
如不需要以上操作,一般给0即可
返回值:成功返回映射的虚拟内存的首地址,失败返回(void*)-1
int shmdt(const void *shmaddr);
功能:取消映射
shmaddr:映射过的虚拟内存首地址
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:删除/控制共享内存
shmid:IPC标识符
cmd:
IPC_STAT 获取共享内存属性信息 buf输出型参数
IPC_SET 修改共享内存的属性 buf输入型参数
IPC_RMID 删除共享内存 buf给NULL即可
编程模型:
进程A 进程B
创建共享内存 获取共享内存
映射共享内存 映射共享内存
写数据到内存并通知 接到通知就读内存数据
接到通知就读内存数据 写数据到内存并通知
取消映射 取消映射
删除共享内存
消息队列:
基本特点:是一个由内核维护管理的数据链表,通过消息类型来匹配正确后收发数据
int msgget(key_t key, int msgflg);
功能:创建\获取消息队列
key:IPC键值
msgflg:
IPC_CREAT 创建消息队列,已存在时会直接获取
IPC_EXCL 配合CREAT,如果已存在则返回错误
获取时直接给0即可
注意:如果是创建IPC_CREAT,需要在后面额外按位或这段消息队列的读写权限IPC_CREAT|0644
返回值:成功返回该消息队列的IPC标识符,失败返回-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);m
功能:向消息队列发送消息包
msqid:IPC标识符
msgp:要发送的消息包的首地址
struct msgbuf {
long mtype; //第一个成员必须是long类型的消息类型
char mtext[1]; //根据需要存储数据
};// 该结构由程序员自己设计
msgsz:只需要数据的字节数,不包括消息类型
msgflg:
阻塞一般写0
IPC_NOWAIT 当消息队列满时,不等待立即返回
返回值:成功0 失败-1
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列中接收读取数据
msqid:IPC标识符
msgp:消息包内存首地址
msgsz:数据内存的字节数大小,不包括消息类型,因为不确定对方发送的字节数,因此建议给大一点
msgtyp:要接收的消息类型
>0 读取消息类型等于msgtyp的消息
=0 读取消息队列中的第一条消息
<0 读取消息队列中的消息类型小于等于abs(msgtyp)的消息如果同时有多个,则读取最小的那一个
msgflg:
阻塞等待一般给0即可
IPC_NOWAIT 如果当时消息类型没有匹配,则不阻塞立即返回
返回值:成功返回读取到的数据字节数,失败-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:删除/控制消息队列
msqid:IPC标识符
cmd:
IPC_STAT 获取消息队列属性信息 buf输出型参数
IPC_SET 修改消息队列的属性 buf输入型参数
IPC_RMID 删除消息队列 buf给NULL即可
编程模型:
进程A 进程B
创建消息队列 获取消息队列
发送消息msg-a 接收msg-a消息
接收msg-b消息 发送消息msg-b
删除消息队列
信号量:
基本特点:由内核维护的一个"全局变量",用于记录进程之间共享资源的数量,限制进程对共享资源的访问
信号量更像是一种数据操作锁,本身是不具备数据交换功能,而是通过控制其他的通信资源(共享内存、消息队列、文件等)来实现进程间通信的协调
1、如果信号量的值>0,说明又可以使用的资源,使用时需要将信号量-1,然后再使用
2、如果信号量的值等于0,说明没有资源可以使用,此时进程会进入休眠,直到信号量的值大于0,进程就会被唤醒,执行步骤1
3、当资源使用完毕后,需要将信号量+1,正在休眠的进程就可以被唤醒执行步骤1
int semget(key_t key, int nsems, int semflg);
功能:创建\获取信号量的IPC标识符
key:IPC键值
nsems:信号量的数量
semflg:
IPC_CREAT 创建信号量,已存在时会直接获取
IPC_EXCL 配合CREAT,如果已存在则返回错误
获取时直接给0即可
注意:如果是创建IPC_CREAT,需要在后面额外按位或这段信号量的读写权限IPC_CREAT|0644
返回值:成功返回该信号量的IPC标识符,失败返回-1
int semop(int semid, struct sembuf *sops,size_t nsops);
功能:对某个或某些信号量进行加减操作
semid:IPC标识符
sops:
struct sembuf{
unsigned short sem_num; // 信号量的下标
short sem_op; //
1 信号量+1
-1 信号量尝试-1,如果为0则休眠阻塞
0 等待信号量的值为0,否则阻塞休眠
short sem_flg;
0 阻塞
IPC_NOWAIT 不阻塞
SEM_UNDO 如果进程终止了没有手动还原信号量+1,系统会自动还原+1
};
nsops:表示sops指针指向多少个连续的结构体,意味着要加减多少个信号量,一般每次只操作一个时写1即可
int semctl(int semid, int semnum, int cmd, ...);
功能:删除\控制信号量
semid:IPC标识符
semnum:第几个信号量 下标从0开始
cmd:
IPC_STAT 获取信号量属性
IPC_SET 设置信号量属性
IPC_RMID 删除信号量
GETALL 获取所有信号量的值
GETNCNT 获取正在等待减信号量的进程数
GETVAL 通过返回值获取下标为semnum信号量的值
GETZCNT 获取正在等待信号量的值为0的进程数
SETVAL 设置下标为semnum信号量的值
SETALL 设置所有信号量的值
...:
union semun {
int val; // 用于设置信号量的值
struct semid_ds *buf;// 用于获取\设置信号量的属性
unsigned short *array; //用于批量设置\获取信号量的值
};
使用流程:
1、创建信号量
2、设置信号量管理的资源数
3、减\加信号量
4、删除信号量
标签:返回,IPC,char,操作系统,04,int,笔记,信号量,进程
From: https://www.cnblogs.com/c-learnmore/p/17647091.html