目录一、 多进程理论基础1.1多进程相关概念1.2 进程的内存管理1.3进程和程序的区别1.4 进程的种类1.5 进程PID的概念1.6特殊的进程1.7有关进程操作的指令1.8 进程状态的切换二.多进程的实现2.1进程的创建fork2.2 父子进程号的获取getpid、getppid2.3 进程退出exit/_exit2.4 进程资源回收wait、waitpid2.6 僵尸进程和孤儿进程三、 进程间通信IPC3.1无名管道3.2 有名管道3.4 信号一、 多进程理论基础1.1多进程相关概念1进程是程序的一次执行过程有一定的生命周期包含了创建态、就绪态、执行态、挂起态、死亡态2进程是计算机资源分配的基本单位系统会给每个进程分配0--4G的虚拟内存其中0--3G是用户空间3--4G是内核空间其中多个进程中0--3G的用户空间是相互独立的但是3--4G的内核空间是相互共享的用户空间细分为栈区、堆区、静态区3 进程的调度机制时间片轮询上下文切换机制4 并发和并行的区别并发针对于单核CPU系统在处理多个任务时。实现多个任务进在宏观上感觉是多个任务同时执行的操作同一时刻只有一个任务在被CPU处理并行是针对于多核CPU而言处理多个任务时同一时间每个CPU处理的任务之间是并行的实现的是真正意义上多个任务同时执行的1.2进程的内存管理1 物理内存内存条上硬件上真正存在的存储空间2 虚拟内存程序运行后通过内存映射单元将物理内存映射出4G的虚拟内存共进程使用1.3进程和程序的区别进程是动态的进程是程序的一次执行过程是有生命周期的进程会被分配0--3G的用户空间进程是在内存上存着的程序是静态的没有所谓的生命周期程序存储在磁盘设备上的二进制文件1.4进程的种类交互进程批处理进程守护进程1.5进程PID的概念1PID: 进程号进程号是一个大于等于0的整数值是进程的唯一标识不可能重复。2PPID: 父进程号系统中允许的每个进程都是拷贝父进程资源得到的1.6特殊的进程0号进程idel他是由linux操作系统启动后运行的第一个进程也叫空闲进程当没有其他进程运行时会运行该进程。他也是1号进程和2号进程的父进程1号进程init他是由0号进程创建出来的这个进程会完成一些硬件的必要初始化工作除此之外还会收养孤儿进程2号进程kthreadd也称调度进程这个进程也是由0号进程创建出来的主要完成任务调度问题1.7有关进程操作的指令ps -ef:能够显示进程之间的关系ps -ajx:能够显示当前进程的状态S休眠态 R运行态 T停止态 Z僵尸态 前台运行ps -aux:可以查看当前进程对CPU和内存的占用率top动态查看进程的相关属性kill指令发送信号的指令使用方式kill -信号号 进程号egkill -2/-SIGINT 进程号查看能够发送的信号有哪些kill -lpidof查看进程的进程号使用方式pidof 进程名egpidof a.out1.8进程状态的切换1、查看停止进程的作业号jobs -l2、实现将停止的进程进入后台运行状态bg 作业号3、实现将后台运行的进程切换到前台运行fg 作业号4、直接将可执行程序后台运行./可执行程序 二.多进程的实现注意可以使用man手册查看具体详情这里只是精简化的2.1进程的创建fork进程的创建过程是子进程通过拷贝父进程得到的新进程的创建直接拷贝父进程的资源所以这个拷贝过程系统通过一个函数fork来自动完成pid_t fork(void);功能通过拷贝父进程得到一个子进程返回值成功在父进程中得到子进程的pid在子进程中的到0失败返回-1并置位错误码pid_t pid-1; pidfork(); if(pid0){printf(我是父进程\n);} else if(pid0){printf(我是子进程\n);} else{ perror(fork error); return -1; }2.2父子进程号的获取getpid、getppidpid_t getpid(void);功能获取当前进程的进程号pid_t getppid(void);功能获取当前进程的父进程pid号2.3进程退出exit/_exitexit(EXIT_SUCCESS);//刷新缓冲区并退出进程---把缓冲区里还没写出去的数据立刻写到文件/屏幕上_exit(EXIT_SUCCESS); //不刷新缓冲区退出进程---直接丢了数据没了2.4进程资源回收wait、waitpidwait是阻塞回收任意一个子进程的资源函数waitpid可以阻塞也可以非阻塞完成对指定的进程号进程资源回收pid_t wait(int *status)//参数接收子进程退出时的状态一般写NULLpid_t waitpid(pid_t pid, int *status, int options);//进程号接受子进程状态选择阻塞非阻塞参数一0:表示回收指定的进程进程号位pid常用-1表示回收任意一个子进程常用参数三0 阻塞WNOHANG 非阻塞常用wait(NULL); //阻塞回收一个子进程资源,只有回收了子进程资源后,父进程才继续向后执行回收多个子进程资源要用多个waitwaitpid(-1, NULL, WNOHANG);//非阻塞回收子进程资源waitpid(-1, status, 0);等价于 wait(status)2.6僵尸进程和孤儿进程1 孤儿进程当前进程还正在运行其父进程已经退出了。说明每个进程退出后其分配的系统资源应该由其父进程进行回收否则会造成资源的浪费2 僵尸进程当前进程已经退出了但是其父进程没有为其回收资源三、 进程间通信IPC3.1无名管道在内核空间创建出一个管道通信,一个进程可以将数据写入管道,经由管道缓冲到另一个进程中读取无名管道顾名思义就是没有名字的管道会在内存中创建出该管道不存在于文件系统随着进程结束而消失无名管道仅适用于亲缘进程间通信不适用于非亲缘进程间通信int pipe(int fildes[2]);功能创建一个无名管道并返回该管道的两个文件描述符参数是一个整型数组用于返回打开的管道的两端的文件描述符 fildes[0]表示读端fildes[1]表示写端返回值成功返回0失败返回-1并置位错误码#include cstdio #include iostream #include myhead.h using namespace std; int main() { int fildes[2]; //创建无名管道并返回该管道的两端文件描述符 if (pipe(fildes) -1) { perror(pipe error); return -1; } cout fildes[0] fildes[0] fildes[1] fildes[1] endl;; pid_t pid -1; pid fork(); if (pid 0) { close(fildes[0]);//不用就关掉 //父进程写操作 char buf[128] hello; write(fildes[1], buf, sizeof(buf)); close(fildes[1]); wait(NULL);//等待回收子进程 } else if(pid0) { //子进程读操作 close(fildes[1]);//不用就关掉 char buf[128] ; read(fildes[0], buf, sizeof(buf)); cout receive father: buf endl; close(fildes[0]); exit(EXIT_SUCCESS);//子进程结束 } else { perror(fork error); return -1; } // 注意只有管道文件两端全部被关闭之后这个管道文件才会在内存中消失 return 0; }管道通信特点:1、管道可以实现自己给自己发消息2、对管道中数据的操作是一次性的当管道中的数据被读取后就从管道中消失了再读取时会 被阻塞3、管道文件的大小64K4、由于返回的是管道文件的文件描述符所以对管道的操作只能是文件IO相关函数,但是不可以 使用lseek对光标进行偏移,必须做到先进先出5、对管道的操作只能是文件IO相关函数,但是不可以使用lseek对光标进行偏移,必须做到先进先出6、管道通信是半双工通信方式单工只能进程A向B发送消息(传呼)半双工同一时刻只能A向B/B向A发消息(对讲机)全双工任意时刻AB可以互相通信3.2有名管道1 顾名思义就是有名字的管道文件会在文件系统中创建一个真实存在的管道文件2 既可以完成亲缘进程间通信也可以完成非亲缘进程间通信int mkfifo(const char *pathname, mode_t mode);功能创建一个管道文件并存在与文件系统中参数1管道文件的名称参数2管道文件的权限内容详见open函数的mode参数返回值成功返回0失败返回-1并置位错误码注意管道文件被创建后其他进程就可以进行打开读写操作了但是必须要保证当前管道文件的两端都打开后才能进行读写操作否则函数会在open处阻塞示例mkfifo.cpp#include iostream #include myhead.h int main() { //创建管道文件 if (mkfifo(./mkfifo, 0644) -1) { perror(mkfifo error); return -1; } cout pipe creates success endl; return 0; }name-send.cpp#include myhead.h int main() { int sfd -1; //打开管道文件 if ((sfd open(./mkfifo, O_WRONLY)) -1) { perror(open error); return -1; } //写操作 char buf[125] ; while (1) { //在终端输入 cout enter:; fgets(buf, sizeof(buf), stdin); buf[strlen(buf) - 1] 0;//最后的回车符换成\0 write(sfd, buf, sizeof(buf)); //循环写入quit退出 if (strcmp(buf, quit) 0) { break; } } close(sfd); return 0; }name-recv.cpp#include myhead.h int main() { int rfd -1; //打开管道文件 if ((rfd open(./mkfifo, O_RDONLY)) -1) { perror(open error); return -1; } //读操作 char buf[125] ; while (1) { bzero(buf, sizeof(buf));//将容器清空 read(rfd, buf, sizeof(buf)); cout receive data: buf endl; //循环读取quit退出 if (strcmp(buf, quit) 0) { break; } } close(rfd); return 0; }注意先运行mkfifo.cpp形成mkfifo文件若文件已存在会报错创建的管道文件要和打开管道的名字一致3.4信号信号是软件模拟硬件的中断功能信号是软件实现的中断是硬件实现的信号通信是属于异步通信工作同步表示多个任务有先后顺序的执行例如按流程办理业务异步表示多个任务没有先后顺序执行例如你在写作业你妈妈在做饭1、一共可以发射62个信号前32个是稳定信号后面是不稳定信号2、常用的信号SIGHUP当进程所在的终端被关闭后终端会给运行在当前终端的每个进程发送该信号默认结束进程SIGINT中断信号当用户键入ctrl c时发射出来SIGQUIT退出信号当用户键入ctrl /是发送退出进程SIGKILL杀死指定的进程SIGPIPE当管道破裂时会发送该信号SIGSTOP暂停进程当用户键入ctrlz时发射SIGCHLD当子进程退出后会向父进程发送该信号3、有两个特殊信号SIGKILL和SIGSTOP这两个信号既不能被捕获也不能被忽略对应信号的处理方式有三种捕获、忽略、默认对信号的处理函数signalsighandler_t signal(int signum, sighandler_t handler);功能将信号与信号处理方式绑定到一起参数1要处理的信号参数2处理方式SIG_IGN忽略SIG_DFL默认一般信号的默认操作都是杀死进程typedef void (*sighandler_t)(int)用户自定义的函数捕获--自己写函数处理返回值成功返回处理方式的起始地址失败返回SIG_ERR并置位错误码注意SIGKILL、SIGSTOP不能当这个函数的参数执行哪个操作都不行#include myhead.h void handler(int signo) { if (signo SIGINT) { cout 用户键盘输入了ctrlc endl; } } int main() { //SIGINT绑定SIG_DFL此时这个信号就会被忽略 //if (signal(SIGINT, SIG_IGN) SIG_ERR) //{ // perror(signal error); //} //SIGINT绑定handler就执行handler操作 if (signal(SIGINT, handler) SIG_ERR) { perror(signal error); } while (1) { cout support endl; sleep(1); } return 0; }信号发送函数kill、raiseint kill(pid_t pid, int sig); //进程号 要发送的信号功能向指定进程或进程组发送信号参数1进程号或进程组号0:表示向执行进程发送信号-1向所有进程发送信号返回值成功返回0失败返回-1并置位错误码int raise(int sig);功能向自己发送信号等价于kill(getpid(), sig);参数要发送的信号返回值成功返回0失败返回非0数组eg: kill(getpid(),SIGKILL); raise(SIGKILL);总结信号可以完成多个进程间通知作用但是不能进行数据传输功能(传输用管道)还有一部分下次再更如果对你有帮助的话欢迎点赞哦