fork() #进程创建
pid_t fork() 没有参数,调用则创建一个进程
子进程被创建后,继承父进程资源数据
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
int main(void)
{
pid_t pid;
pid = fork();
printf("printf msg 01....\n");
printf("printf msg 02....\n");
while(1)
sleep(1);
return 0;
}
~
一个父进程多个子进程
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
//parent start
int main(void)
{
pid_t pid;
int i;
for(i=0;i<5;i++){
pid = fork();
if(pid == 0 )
break; //防止子子孙孙无穷创建 所以到子进程这就break掉了
}
if(pid > 0){
printf("parent pid %d jobs....\n",getpid());
while(1)
sleep(1);
}else if(pid == 0){
//child start
printf("child pid %d jobs....\n",getpid());
while(1)
sleep(1);
exit(0);
//child end
}else{
perror("fork failed");
exit(0);
}
return 0;
//parent end
}
僵尸进程
子进程退出后,资源没有释放完毕,PCB残留,这时的子进程称为僵尸进程,结果为内存泄漏
系统内所有进程结束后都会变成僵尸进程
wait
pid_t zpid=wait(NULL);
调用一次,回收一个僵尸进程,如果有多个进程需要循环调用。
wait是阻塞函数,会等待子进程结束后进行回收
可以传入一个int* status去承接PCB中的信息,来实现验尸的目的,也可以传NULL放弃验尸。
后续通过status进行验证处理,获取子进程的退出原因
执行wait函数时,参数为null,表示只释放PCB内存,但是不采集子进程退出原因,不进行验尸操作
返回僵尸进程的pid
zpid>0 僵尸进程回收成功
zpid==-1 回收失败,原因:没有子进程却执行回收操作
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
int main(void)
{
pid_t pid;
pid = fork();
if(pid > 0){
printf("parent jobs....\n");
pid_t zpid;
if((zpid = wait(NULL))>0){
printf("parent wait child success,zpid %d\n",zpid);
}
while(1)
sleep(1);
}else if(pid == 0){
//child start
printf("child jobs....\n");
sleep(10);
printf("child exit...\n");
exit(0);
}else{
perror("fork failed");
exit(0);
}
printf("hahaha\n");
return 0;
}
~
wait会等待子进程完成后再去回收
waitpid
定义
wait的升级版
pid_t zpid = waitpid(pid_t pid, int* status, WNOHANG);
waitpid更多的回收方式,还支持非阻塞回收。
特点
1.更多的回收方式
pid参数不同有着不同的含义
(1) pid>0 指定子进程id,回收特点的子进程
(2) -1 回收任意子进程,最常使用
(3) 0 同组回收,表示回收与当前进程同组的所有子进程
(4) pid<-1 如-3000 表示回收进程组id为3000的其中的子进程,跨组回收
2.非阻塞回收
可以让父进程在回收的过程中交替执行的自己的任务,无需一直等待子进程
返回值
返回值>0,回收成功
返回值=0,非阻塞返回(子进程未结束,不需要回收)
返回值=-1,回收失败
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>
void jobs(void)
{
printf("Parent Exec myJobs Once...\n");
}
int main(void)
{
pid_t pid;
int i = 0;
for(i;i<5;i++){
pid = fork();
if(pid==0)
break;
}
if(pid > 0){
pid_t zpid;
while((zpid = waitpid(-1,NULL,WNOHANG))!=-1)
{
if(zpid > 0)
{
printf("parent wait child success,zpid %d\n",zpid);
}else if(zpid == 0){
jobs();
}
sleep(1);
}
}else if(pid == 0){
printf("child pid %d\n",getpid());
sleep(i*5);
exit(i*5);
}else{
perror("fork call failed");
exit(0);
}
return 0;
}
父进程任务与回收交替执行 首先父进程创建五个子进程 然后waitpid非阻塞回收 zpid大于0说明回收成功一个子进程 zpid=0非阻塞回收 说明子进程还在跑没完事先不回收 执行父进程自己的任务 为了防止回收太快加了sleep(1)
每个子进程的i不同 所以sleep时间也不同
所有子进程全部被回收后 waitpid返回-1 结束回收操作 结束while循环
最后的while sleep防止父进程被回收
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
int main(void)
{
pid_t pid;
int i = 0;
for(i;i<2;i++){
pid = fork();
if(pid == 0)
break;
}
if(pid>0){
printf("parent waitpid....\n");
pid_t zpid;
int status;
while((zpid = waitpid(-1,&status,WNOHANG))!=-1){
if(zpid){
if(WIFEXITED(status))
printf("wait success, zpid %d ,exit or return, %d\n",zpid,WEXITSTATUS(status));
if(WIFSIGNALED(status))
printf("wait success, zpid %d ,child process kill , signal %d\n",zpid, WTERMSIG(status));
}
}
}else if(pid == 0){
if(i==0){
while(1){
printf("child %d pid %d , Runing..\n",i,getpid());
sleep(1);
}
}
else if(i==1){
printf("child %d pid %d , exit...\n",i,getpid());
exit(16);
}
}else{
perror("Fork Failed");
}
return 0;
}
两个子进程一个正常死亡 一个自己用信号死亡 用status查看死因 结果不同。。。
excel函数,进程重载
需要重载的进程app进程先预期动date 获得date进程,然后将date进程里面的0~3G的用户空间替换自己原有的用户空间
参数的最后要加NULL停止标志,参数到NULL停止,不加会报错
which 获取所需重载的命令路径
个人理解就是用子进程的用户空间去重载别的功能 直接重载现成的功能 免去了自己写的麻烦
重载时间函数date
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
int main(void)
{
pid_t pid;
pid = fork();
if(pid>0){
printf("parent waiting for child..\n");
wait(NULL);
}else if(pid == 0){
printf("child pid %d exists...\n",getpid());
execl("/bin/date","date",NULL);
printf("child exec success..\n");
exit(0);
}else{
}
return 0;
}
进程间通信
pipe匿名管道
为了便于开发者使用管道可以使用pipe函数创建管道,创建成功后会生成两个文件描述符,分别指向管道的读端fds[0]和写端fds[1]。匿名管道只能再有亲缘关系的进程之间使用
管道的原理
两个进程公用一个管道,实现通信
管道的特点
- 是一种传输介质,可以传输数据
- 管道传输具有方向性
- 管道具备存储能力,虽然不能持久存储,只能暂存
匿名管道只能在有亲缘关系的进程间的通信
创建
pipe(int fds[2]);
注意:管道创建要在 创建子进程之前完成, 避免子进程创建管道
回收
1.每个指向管道的描述符都是一个引用计数,如果引用计数为0,系统自动释放管道空间,否则管道一直存在于内核层
2.管道使用时要确定通信方向,父写子读,子写父读,父子进程将不用的描述符关闭 关闭不用的
3.pipe_fd使用完毕要close 引用计数-1 用完要关闭
4.shutdown可以直接将引用计数清0
进程间用户层是独立的 内核层是共享的 所以创建匿名管道通通过内核层来实现进程间通信
父端有读写 子断也有读写 四次close 管道自动被系统释放
子进程会继承父进程的fds[2] pipe等
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/fcntl.h>
#include<pthread.h>
#include<signal.h>
#include<sys/wait.h>
//父写子读,管道访问
#define MESSAGE "Hi,Son can you hear me?"
int main(void)
{
int fds[2];
pipe(fds);
pid_t pid;
pid = fork();
if(pid>0){
close(fds[0]);
//向管道发送数据
write(fds[1],MESSAGE,strlen(MESSAGE));
printf("parent send msg success..\n");
close(fds[1]);
wait(NULL);
}else if(pid == 0){
close(fds[1]);
char buffer[1024];
bzero(buffer,sizeof(buffer));
read(fds[0],buffer,sizeof(buffer));
close(fds[0]);
printf("child read msg = %s\n",buffer);
exit(0);
}else{
perror("fork call failed");
exit(0);
}
return 0;
}
匿名管道使用时的四种特殊情况
这四种情况具有普遍意义
1.管道的读写两端存在但是写端未向管道内写数据,读端读取完管道数据后如果管道为null,再次读阻塞
2.管道读写两端存在,但是读端未读取管道数据,写端写满管道后,再次写阻塞
3.管道写端关闭,读端读取管道剩余内容后,再次读读到0
4.管道读端关闭,写端向管道写数据,系统会发送SIGPIPE信号,杀死写端进程,读端关闭的情况下不允许写端访问管道
在一个C/S模型中,客户端发送了一条请求消息之后客户端异常关闭,服务端回复结果时异常关闭,服务端异常关闭原因?
FIFO命名管道
是一个内核缓冲区,为环形队列结构, 大小为4k,命名管道可以解决无关联进程间的通信
特点:
管道通信与传统的文件传输不同,读取文件内容并不会除文件内容,但是管道是队列结构,出队的数据会从管道消失
可以持久化存储的磁盘文件并不适合作为传输介质
原理:
简单理解,管道文件是一个指向内核管道缓冲区的指针,所有向管道文件读写的操作,都会重定向到内核管道中
创建管道文件 vim编译不了这个文件 文件为黄色
fd为文件描述符
写端:
#define MSG "koumoukoumo"
int main(void)
{
//open file
int fd = open("管道",O_WRONLY);
//write msg
write(fd,MSG,strlen(MSG));
close(fd);
return 0;
}
读端:
int main(void)
{
char buf[1500];
bzero(buf,sizeof(buf));
//open file
int fd = open("管道",O_RDONLY);
read(fd,buf,sizeof(buf));
printf("fifo read msg %s\n",buf);
close(fd);
return 0;
}
管道文件的访问是要凑齐两种权限的 即使 RDWR,如果某个进程以一种权限打开管道文件, open函数会立即阻塞,等待另一种权限。即使只有一个进程 只要满足读写权限,就可以成功打开管道了
(先执行写端./write 会在open阻塞 open函数读写兼备的时候才会进行
然后./write)
mmap文件映射
mmap主要用来做内存映射的,可以将虚拟内存和磁盘上的文件直接映射。正常来说我们在写文件读文件的时候是需要使用系统调用api来进行,比如说read/write,这两个系统调用读写文件的方式是需要进行两次拷贝的,从用户空间拷贝到内核空间,然后从内核空间再拷贝到磁盘,而mmap将文件的地址直接映射到虚拟内存,这样,我们直接往这个地址读/写内容,可以像操作malloc申请出来的空间地址一样,写到这个地址,内容就直接在文件中了,减少了一次拷贝,提高了效率。这样一个公共的内存区域,也可以用来进程间通信。
mmap函数说明
头文件
#include <sys/mman.h>
参数说明
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
参数说明:
addr: 入参,如果这个地址为null那么内核将自己为你指定一个地址,如果不为null,将使用这个地址作为映射区的起始地址
length: 映射区的大小(<=文件的大小)
prot: 访问属性,一般用PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags:这个参数是确定映射的更新是否对映射相同区域的其他进程可见,以及是否对基础文件进行更新
MAP_SHARED: 共享此映射,映射的更新对映射相同区域的其他进程可见
MAP_PRIVATE: 创建写时专用拷贝映射,映射的更新对映射的其他进程不可见,相同的文件,并且不会传递到基 础文件。
我们一般用MAP_SHARED,这两个权限是限制内存的,而不限制文件
fd: 被映射的文件句柄
offset: 默认为0,表示映射文件全部。偏移未知,需要时4K的整数倍。
返回值:成功:被映射的首地址 失败:MAP_FAILED (void *)-1
映射方式
共享映射 MAP_SHARED,共享映射中有一种机制叫做sync同步机制,对一端的修改会实时同步到另一端,这也是通过文件共享映射实现进程通信的基础
私有映射 MAP_PRIVATE,私有映射又叫只读映射,是指仅将映射文件中的内容拷贝到进程的映射内存之中,后续进程对映射内存中内容的修改不会影响到映射文件中的内容
释放内存
int munmap(void *addr, size_t length);
参数说明:
addr: 被映射的首地址
length: 映射的长度
返回值: 0:成功 -1:失败
例:创建一个映射文件里面写点点东西 然后文件映射内容给他改了
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/fcntl.h>
#include<pthread.h>
#include<signal.h>
#include<sys/mman.h>
int main(void)
{
/*打开映射文件*/
int fd = open("MAPEFILE",O_RDWR);
/*获取文件大小*/
int size = lseek(fd,0,SEEK_END);
/*共享映射,返回值的指针类型关系到后续如何访问内存*/
int *ptr;
if((ptr = mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED){
perror("mmap failed");
exit(0);
}
close(fd);
ptr[0] = 0x34333231;
printf("change mapfile success\n");
//释放映射
munmap(ptr,size);
return 0;
}
munmap函数
ftruncate函数
头文件 | #include<unistd.h>、#include<sys/types.h> |
功能 | 如果文件初始大小小于我们所要求的大小,便在后面不断填充'\0',直到文件截断至我们要求的大小; 如果文件初始大小大于我们所要求的大小,便在文件目标大小处加入'\0'来截断文件并删除后面的内容,使文件达到我们要求的大小 |
语法 | int ftruncate(文件描述符 , 想要截断到多大) |
返回值 | 成功则返回 0,失败返回 -1, 错误原因存于errno |
进程通过文件共享映射实现通信的代码实现:
文件大小设定为一个结构体大小,写文件也每次写一个结构体大小,读的不及时新的结构体会把旧的结构体覆盖
- 确定消息结构体大小
- 根据消息结构体大小,确定映射文件大小。大小为消息结构体的倍数
- 拓展空文件,使用截断方式,将映射文件拓展成一个指定的大小
这里需要两个进程,一个读一个写,我们创建两个.c文件,分别为map_write.c和map_read.c
写进程:map_write.c:
//map_write.c
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
/*设置一个定时器*/
#define timeout 1
/*定义一个结构体,映射文件里存放的就是结构体消息*/
typedef struct
{
int code;
char msg[100];
}msg_t;
int main(void)
{
/*1.创建映射文件,读写打开*/
int fd = open("MAP" , O_RDWR|O_CREAT , 0664);
/*2.由于是往文件里写结构体类型的消息,所以要将文件拓展至一个结构体的大小*/
ftruncate(fd , sizeof(msg_t));
/*3.建立映射关系*/
/*由于映射文件里是结构体类型的消息,所以要定义一个结构体类型的指针来接取返回值*/
msg_t* ptr = NULL;
//如果映射创建失败,程序直接退出
if((ptr = mmap(NULL , sizeof(msg_t) , PROT_READ|PROT_WRITE , MAP_SHARED , fd , 0)) == MAP_FAILED)
{
perror("映射关系建立失败!\n");
exit(0);
}
/*映射关系建立成功,关闭文件*/
close(fd);
/*初始化结构体里面的内容*/
ptr->code = 0;
bzero(ptr->msg , sizeof(ptr->msg));
/*循环向映射文件内写入内容*/
while(1)
{
ptr->code++;
sprintf(ptr->msg , "this is a test message , code = %d\n" , ptr->code);
sleep(timeout);
}
/*关闭映射*/
munmap(ptr , sizeof(msg_t));
exit(0);
}
读进程:map_read.c:
//map_read.c
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
/*设置一个定时器*/
#define timeout 1
/*定义一个结构体,映射文件里存放的就是结构体消息*/
typedef struct
{
int code;
char msg[100];
}msg_t;
int main(void)
{
/*先打开文件*/
int fd = open("MAP" , O_RDWR);
/*由于该进程是读取内容,所以不能扩展映射文件的大小,否则会覆盖文件里的内容*/
/*建立映射关系*/
/*由于映射文件里是结构体类型的消息,所以要定义一个结构体类型的指针来接取返回值*/
msg_t* ptr = NULL;
if((ptr = mmap(NULL , sizeof(msg_t) , PROT_READ|PROT_WRITE , MAP_SHARED , fd , 0)) == MAP_FAILED)
{
perror("读进程映射关系建立失败!\n");
exit(0);
}
/*映射关系建立成功,关闭文件*/
close(fd);
/*循环向映射文件内读取内容*/
while(1)
{
printf("%s\n" , ptr->msg);
sleep(timeout);
}
/*关闭映射*/
munmap(ptr , sizeof(msg_t));
exit(0);
}
mmp可以进行大文件切割处理:
信号
【Linux】信号简介与触发信号的几种方式_linux中控制台输入exit是什么信号-CSDN博客
【Linux】信号屏蔽与信号捕捉的原理与实现(附图解与代码实现)_linux 屏蔽信号-CSDN博客