Linux进程相关复习

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]。匿名管道只能再有亲缘关系的进程之间使用

管道的原理

两个进程公用一个管道,实现通信

管道的特点

  1. 是一种传输介质,可以传输数据
  2. 管道传输具有方向性
  3. 管道具备存储能力,虽然不能持久存储,只能暂存

缺点

匿名管道只能在有亲缘关系的进程间的通信

创建

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

进程通过文件共享映射实现通信的代码实现: 

文件大小设定为一个结构体大小,写文件也每次写一个结构体大小,读的不及时新的结构体会把旧的结构体覆盖

  1. 确定消息结构体大小
  2. 根据消息结构体大小,确定映射文件大小。大小为消息结构体的倍数
  3. 拓展空文件,使用截断方式,将映射文件拓展成一个指定的大小

这里需要两个进程,一个读一个写,我们创建两个.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博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值