进程控制
1.wait函数
函数原型: pid_t wait(int *status)
- 函数作用:阻塞并等待子进程退出;回收子进程残留资源;获取子进程结束状态(退出原因)
- 返回值:成功(清理掉的子进程id),失败(-1,没有子进程)
- Status参数:子进程的退出状态,是传出参数
WIFEXITED(status):为非0 进程正常结束;获取进程退出状态
WIFSIGNALED(status):为非0 进程异常终止;取得进程终止的信号编号
//wait函数 父进程对子进程进行回收
#include <sys/wait.h> //wait函数必须的头文件
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(){
pid_t pid=fork();
if(pid<0){ perror("fork error"); return -1;}
else if(pid>0){
printf("father:pid=%d",getpid());
pid_t wpid=wait(NULL);
printf("wpid=%d",wpid);
}else printf("child:pid=%d",getpid());
return 0;
}
- 结果截图
对于wait函数参数的说明及代码验证,可在linux 通过man [2] wait查询
WIFEXITED(wstatus)正常退出
returns true if the child terminated normally, that is, by calling exit(3) or _exit(2), or by returning from main().
WEXITSTATUS(wstatus)
returns the exit status of the child. This consists of the least significant 8 bits of the status argument that the child specified in a call to exit(3) or _exit(2) or as the argument for a return statement in main(). This macro should be employed only if WIFEXITED returned true.
WIFSIGNALED(wstatus)信号杀死
returns true if the child process was terminated by a signal.
WTERMSIG(wstatus)
returns the number of the signal that caused the child process to terminate. This macro should be employed only if WIFSIGNALED returned true.
//在父进程更改
int status;
pid_t wpid=wait(&status);
if(WIFEXITED(status)){//正常退出
printf("child normal exit,status=%d",WEXITSTATUS(status));
}else if(WIFSIGNALED(status)){//被信号杀死
printf("child killed by signal,signo=%d",WTERMSIG(status));
}
- 结果截图
waitpid函数
pid_t waitpid(pid_t pid,int *status,int options);
- 参数说明:
pid=-1 等待任意子进程,与wait等效
pid>0 等待进程id与pid相等的子进程
pid=0 等待进程组id与目前进程相同的任何子进程,即和调用waitpid()函数的进程在同一个进程组的进程
pid<-1 等待其组id等于pid的绝对值的任意子进程,适用于子进程在其他组的情况
status:子进程的退出状态,用法同wait函数
options:设置为WNOHANG,函数非阻塞,设置为0,函数阻塞 - 函数返回值
>0:返回回收的子进程id
-1:无子进程
=0,且第三个参数为WNOHANG,意味着子进程正在运行
while(1){
pid_t wpid=waitpid(-1,&status,WNOHANG);
if(wpid>0){//有子进程退出
if(WIFEXITED(status)) printf("child normal exit,status=%d",WEXITSTATUS(status));
else if(WIFSIGNALED(status)) printf("child killed by signal,signo=%d",WTERMSIG(status));
}else if(wpid==-1)//没有子进程
{printf("no child is living,wpid=%d",wpid); break;}
}
总结:
调用一次waitpid或者wait函数,只能回收一个子进程
2.execl函数
函数原型: int execl(const char *path,const char arg,…/(char ) NULL/);
- 参数:
path:要执行的程序的路径
变参arg:要执行的程序需要的参数
arg:仅是占位
arg后面的:命令参数
参数写完之后,加上NULL - 返回值:
若成功,则不返回,不会执行exec函数后面的代码;若失败,会执行execl后面的代码,可用perror打印出错信息
execl函数一般执行自己写的代码
如1.c编译成1.在子进程调用时,通过execl(“./1”,”占位符的随便的名字”,这里没参数,NULL);执行。
出错返回perror(“execl error”);
进程通信
IPC:进程间通信(interprocess communication)
两个进程要想完成数据交换,必须通过内核。
方式有:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。
1.管道
匿名管道pipe
- 管道的实质是内核缓冲区,内部使用环形队列实现。
- 管道有读写两端,且是阻塞的,读写两端为两个文件描述符
- 数据被读走,就不会存在于管道中。
- 数据流向是单向的
- 实现有血缘关系的进程间的通信
函数原型: int pipe(int fd[2]);
- 参数:fd[0]存放管道的读端,fd[1]存放管道的写端。
- 返回值:成功返回0;失败返回-1,并设置errno值。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
/*
1.父进程创建pipe
2.父进程调用fork函数创建子进程
3.父进程关闭一端,子进程关闭一端
4.父进程分别执行read和write通道
*/
int main(){
//创建管道
int fd[2];
int ret=pipe(fd);
if(ret<0){
perror("pipe error");
return -1;
}
pid_t pid=fork();
if(pid<0){perror("fork error"); return -1;}
else if(pid>0){
close(fd[0]);//关闭读端
write(fd[1],"hello world",strlen("hello world"));
}else{
close(fd[1]);
char buf[64];
memset(buf,0x00,sizeof(buf));
int n=read(fd[0],buf,sizeof(buf));//没有数据会阻塞
printf("read over,n=%d,buf=%s\n",n,buf);
}
return 0;
}
- 代码截图
//实现ps aux|grep bash
else if(pid>0){
close(fd[0]);
dup2(fd[1],STDOUT_FILENO);
execlp("ps","ps","aux",NULL);
}else if(pid==0){
close(fd[1]);
dup2(fd[0],STDIN_FILENO);
execlp("grep","grep","bash",NULL);
}
- 结果截图
管道的读写行为
读:有数据read正常读,返回读出的字节数;无数据,若写端全部关闭,read解除阻塞,返回0;若没有全部关闭,read会阻塞。
写:读端全部关闭,管道破裂,进程终止,内核给当前进程发SIGPIPE信号;读端没有全部关闭,缓冲区没满,继续write,否则write阻塞。
FIFO 命名管道
不相关进程也能交换数据。
FIFO是LInux基础文件类型中的一种(文件类型为p,可通过ls -l查看文件类型)。但FIFO文件在磁盘上没有数据块,文件大小为0,仅仅用来标识内核中的一条通道。使用读写,实际操作的是内核缓冲区。
int mkfifo(const char *pathname,mode_t mode);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
//进程A
int main(){
//创建fifo文件
int ret=mkfifo("./myfifo",0777);
if(ret<0){perror("mkfifo error");return -1;}
//打开文件
int fd=open("./myfifo",O_RDWR);
if(fd<0){
perror("open error");
return -1;
}
//写FIFO文件
write(fd,"hello world",strlen("hello world"));
//close(fd);这里不能关闭
return 1;
}
//进程B
int main(){
int fd=open("./myfifo",O_RDWR);
if(fd<0){perror("open error");return -1;}
char buf[64];
memset(buf,0x00,sizeof(buf));
int n=read(fd,buf,sizeof(buf));
printf("n=%d,buf=%s",n,buf);
close(fd);
return 0;
}
2.信号(开销小)
3.共享映射区
4.共享内存
可以支持结构体等多种数据类型
读完之后,数据依旧存在
主要函数:
- shmget 创建/获取共享内存
- shmat(attach) 关联到当前进程地址空间
- 通过指针读写共享内存
- shmdt(detach) 进程分离共享内存
- shmctl 删除共享内存