fork创建子进程后执行的是和父进程相同的程序,子进程往往要调用一种exec函数以执行另一个程序。
其中有六种以exec开头的函数,统称exec函数:常用两种
execlp :p->path :该变量通常来调用系统程序。如:ls、cp、cat
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid > 0)
{
sleep(1);
printf("parent\n");
}
else
{
execlp("ls", "ls", "-l", "-a", "-h", NULL);
}
return 0;
}
execl函数:加载一个进程,通过 路径+程序名 来加载
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid > 0)
{
sleep(1);
printf("parent\n");
}
else
{
execlp("/bin/ls", "ls", "-l", "-a", "-h", NULL); //可加载自定义程序
}
return 0;
}
回收子进程
- 孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称init进程领养孤儿过程。
- 僵尸进程:子进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程。
注:僵尸进程不能使用kill命令清除掉。因为kill命令只是用来终止进程的,而僵尸进程已经终止。
wait函数
该函数有三个功能:
- 阻塞等待子进程退出(子进程没死)
- 回收子进程残留资源
- 获取子进程结束状态(退出原因)
pid_t wait(int* status); 成功:清理掉的子进程ID 失败:-1(没有子进程)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid, wpid;
pid = fork();
if(pid == 0)
{
printf("---child, my parent = %d, going to sleep 10s\n", getppid());
sleep(10);
printf("-----child die-----\n"):
}
else if(pid > 0)
{
wpid = wait(NULL); //回收
if(wpid == -1)
{
perror("wait error");
exit(1);
}
while(1)
{
printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);
}
}
else{
perror("fork");
return 1;
}
return 0;
}
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的原因。宏函数可分为如下三组:
1.WIFEXITED(status) 为非0 -> 进程正常结束
WEXITSTATUS(status) 如果宏为真,使用此宏->获得进程退出状态(exit)的函数
2.WIFSIGNALED(status) 为非0 -> 进程异常终止
WTERMSIG(status) 如果宏为真,使用此宏->获取是进程终止的那个信号的编号
3.不常用了解即可
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid, wpid;
int status;
pid = fork();
if(pid == 0)
{
printf("---child, my parent = %d, going to sleep 10s\n", getppid());
sleep(3);
printf("-----child die-----\n");
exit(76);
}
else if(pid > 0)
{
wpid = wait(&status); //回收
if(wpid == -1)
{
perror("wait error");
exit(1);
}
if(WIFEXITED(status))
{
printf("child exit with %d\n", WEXITSTATUS(status));
}
if(WIFSIGNALED(status))
{
printf("child kelled by %d\n", WEXITSTATUS(status));
}
while(1)
{
printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);
sleep(1);
}
}
else{
perror("fork");
return 1;
}
return 0;
}
waitpid函数
作用同wait,但可以指定pid进程清理,可以不阻塞。
pid_t wiatpid(pid_t pid, int* status, in options); //参数 回收id 回收状态 设置非阻塞状态
IPC方法 InterProcess Communication
7种文件类型
占用磁盘存储
- 文件
d 目录
l 符号链接
:伪文件(不占用磁盘存储)
s 套接字
b 块设备
c 字符设备
p 管道
现常用的进程间通信的方式有:管道、信号、共享映射区、本地套接字
管道:
pipe
管道一般读写行为
fifo:(有名管道)
用于非血缘关系进程间通信
共享内存:
mmap
函数的参数使用注意事项
用于非血缘关系进程间通信
管道的概念:
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。
特性:
本质是一个伪文件
由两个文件描述符引用,一个表示端,一个标识写端
规定数据从管道写端流入管道,从读端流出。
管道的原理:管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道的局限性:
读端只读不写,写端只写不读
数据一旦被读走,便不在管道中存在,不可反复读取。
由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
只能在有公共祖先的进程间使用管道。
pipe函数创建管道
int pipe(int pipefd[2]); 成功:0; 失败:-1
行数调用成功返回r/w两个文件描述符。无需open,需要手动close。规定:fd[0]->r; fd[1]->w。向管道文件读写数据其实是在读写内核缓冲区。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int fd[2];
pid_t pid;
int ret = pipe(fd);
if(ret == -1)
{
perror("pipe error:");
exit(1);
}
pid = fork();
if(pid == -1)
{
perror("pipe error:");
exit(1);
}
else if(pid == 0) //子 读数据
{
close(fd[1]);
char buf[1024];
ret = read(fd[0], buf, sizeof(buf));
if(ret == 0)
{
printf("----\n");
}
write(STDOUT_FILENO, buf, ret);
}
else //父 写
{
close(fd[0]);
write(fd[1], "hello pipe\n", strlen("hello pipe\n"));
}
return 0;
}