linux并发服务器 —— 多进程并发 - 进程间的通信及实践(五)

 进程间的通信

进程是一个独立的资源分配单元,不能在一个进程中直接访问另一个进程的资源;

进程间通信(IPC)的目的:

1. 数据传输 - A进程发送数据给B进程

2. 通知事件 - eg. 进程终止通知父进程

3. 资源共享 - 多个进程之间共享资源,需要内核提供互斥和同步机制

4. 进程控制 - 进程控制另一个进程的执行

匿名管道(管道)

UNIX系统IPC的最古老形式,所有UNIX系统都支持这种通信机制;

ls | wc -l : 统计一个目录中的文件数目;| - 管道符

管道的特点

1.是一个内核内存中维护的缓冲器,存储能力有限,不同操作系统大小不同;

2. 匿名管道没有文件实体,有名管道有文件实体(但不存储数据),可以按照操作文件的方式对管道进行操作;

3. 一个管道是一个字节流,使用管道不存在消息/消息边界的概念;不管写入数据块多大,可以读任意大小的数据块;

4. 先进先出;

5. 管道是半双工的,数据传递方向是单向的;

6. 读数据是一次性操作,读了就没了,并且无法随机访问数据;

7. 匿名管道只能在有亲缘关系的进程之间进行通信;

管道的数据结构 - 循环队列

/*#include <unistd.h>int pipe(int pipefd[2]);功能:创建一个匿名管道 用于进程间通信参数: 是一个传出参数pipefd[0] - 管道的读端pipefd[1] - 管道的写端返回值:成功 - 0失败 - -1管道默认是阻塞的,管道中没有数据read阻塞,管道满了write阻塞
*/
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;// 子进程写 父进程读
int main(){// fork之前创建管道int pipefd[2];int ret = pipe(pipefd);if(ret == -1){perror("pipe");return -1;}pid_t pid = fork();if(pid>0){cout<<"我是你爹"<<endl;while(1){char buf[1024];int len = read(pipefd[0] , buf , sizeof(buf));cout<<"父进程 "<<getpid()<<"读到了: "<<buf<<endl;char str[10] = "hello zz";write(pipefd[1] , str , sizeof(str));sleep(2);}}else if(pid == 0){// 写数据cout<<"我是你儿子"<<endl;while(1){char str[10] = "hello 647";write(pipefd[1] , str , sizeof(str));sleep(2);char buf[1024];int len = read(pipefd[0] , buf , sizeof(buf));cout<<"子进程 "<<getpid()<<"读到了: "<<buf<<endl;}}return 0;
}

查管道大小:

1. ulimit - a

2. fpathconf(int fd , int name); name - _PC_PIPE_BUF 获取管道大小

通过管道实现ps aux

/*实现ps aux | grep父子进程通信 - 子进程 ps aux,结束后发送给父进程 过滤即可pipe() - 创建管道execlp() - 调用ps aux将子进程标准输出stdout_fileno重定向到父进程 - 管道写端 dup2
*/
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;// 子进程写 父进程读
int main(){int fd[2];int ret = pipe(fd);if(ret == -1){perror("pipe");exit(0);}pid_t pid = fork();if(pid > 0){close(fd[1]);char buf[1024] = {0};int len = -1;while((len = read(fd[0] , buf , sizeof(buf) - 1)) > 0){cout<<buf;memset(buf , 0 , 1024);}wait(NULL);}else if(pid == 0){close(fd[0]);dup2(fd[1] , STDOUT_FILENO);execlp("ps" , "ps" , "aux" , NULL);perror("execlp");exit(0);}else{perror("fork");exit(0);}return 0;
}

管道的读写特点

(假设都是阻塞I/O操作)

1. 所有指向管道的写端描述符都关闭(写端引用计数为0),剩余数据都读完后再read会返回0;

2. 如果有指向管道写端的文件描述符没有关闭(引用计数>0),但没有写数据,read会阻塞;

3. 读端都关闭,进程向管道写数据,该进程会收到信号SIGPIPE,通常会导致进程异常终止;

4. 读端没有完全关闭,而持有管道读端的进程也没从管道读数据,写数据写满了就阻塞;

设置管道非阻塞

通过设置文件描述符非阻塞;

// 通过fcntl(fd , F_GETFL)获得状态,再设置可实现管道非阻塞
int flg = fcntl(pipefd[0] , F_GETFL);
flg |= O_NONBLOCK;
int cnt = fcntl(pipefd[0] , F_SETFL , flg);

有名管道(FIFO文件)

提供一个路径名与之关联,有文件的实体,以FIFO文件形式存在文件系统中,没有亲缘关系也能通过有名管道实现通信;

数据结构也是一个环形队列;

与匿名管道的区别:

1. FIFO在文件系统中作为特殊文件存在,但文件里没有内容,内容存在内存里的缓冲区;

2. FIFO退出后,文件会保留;

3. FIFO有名字,不相关进程可以通信;

通过mkfifo 名字创建有名管道,创建后可以用open打开,常见文件I/O函数都可以用;

有名管道的读端写端实践:

/*创建fifo文件1. 命令 - mkfifo 名字2. 函数 - int mkfifo(const char *pathname, mode_t mode);参数:pathname - 管道名称的路径mode - 权限 和open的一样返回值:成功 - 0失败 - -1 并设置 errno#include <sys/types.h>#include <sys/stat.h>*/
// 向管道写数据
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
using namespace std;int main(){int dec = access("fifo" , F_OK);if(dec==-1){cout<<"管道不存在"<<endl;int ret = mkfifo("fifo" , 0664);if(ret == -1){perror("mkfifo");exit(0);}}// 打开管道int fd = open("fifo" , O_WRONLY);if(fd == -1){perror("open");exit(0);}for(int i = 0 ; i<100 ; i++){char buf[1024];sprintf(buf , "hello 647 : %d" , i);cout<<buf<<endl;write(fd , buf , sizeof(buf));sleep(1);}close(fd);return 0;
}// 向管道读数据
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
using namespace std;int main(){// 打开管道int fd = open("fifo" , O_RDONLY);if(fd == -1){perror("open");exit(0);}while(1){char buf[1024] = {0};int len = read(fd , buf , sizeof(buf));if(len == 0){cout<<"写端断开"<<endl;break;}cout<<"get: "<<buf<<endl;}close(fd);return 0;
}

一个为只读而打开一个管道的进程会阻塞,直到有写的权限打开管道,反之同理;

读管道:

        管道中有数据,read返回实际读到的字节数

        管道无数据

                写端全关闭,read返回0

                写端没有全关闭,read阻塞

写管道:

        读端全关闭,异常终止SIGPIPE

        读端没有全关闭

                管道满了,阻塞

                 没满,正常写入即可,返回实际写入字节数

有名管道实现聊天功能

/*实现进程A、B的聊天功能需要两个管道 一个A读B写 一个B读A写进程A:1. 只写打开fifo12. 只读打开fifo23. 循环读写数据while(1){获取键盘录入 fgetswrite fifo1read fifo2}进程B与进程A相反即可while(1){read fifo1获取键盘录入 fgetswrite fifo2}
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
using namespace std;int main(){// 判断管道文件是否存在int ret = access("fifo1" , F_OK);if(ret == -1){ret = mkfifo("fifo1" , 0664);if(ret == -1){perror("mkfifo");exit(0);}}ret = access("fifo2" , F_OK);if(ret == -1){ret = mkfifo("fifo2" , 0664);if(ret == -1){perror("mkfifo");exit(0);}}// 只写开fifo1int fdw = open("fifo1" , O_WRONLY);if(fdw == -1){perror("open");exit(0);}cout<<"打开fifo1成功..."<<"等待写入..."<<endl;// 只读开fifo2int fdr = open("fifo2" , O_RDONLY);if(fdr == -1){perror("open");exit(0);}cout<<"打开fifo2成功..."<<"等待读取..."<<endl;char buf[128];// 循环写读while(1){memset(buf , 0 , sizeof(buf));// 获取输入数据fgets(buf , sizeof(buf) , stdin);// 写入ret = write(fdw , buf , strlen(buf));if(ret == - 1){perror("write");exit(0);}// 读数据memset(buf , 0 , sizeof(buf));ret = read(fdr , buf , sizeof(buf));if(ret<=0){perror("read");exit(0);}cout<<"进程A读到数据 - "<<buf;}close(fdr);close(fdw);}#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
using namespace std;int main(){// 判断管道文件是否存在int ret = access("fifo1" , F_OK);if(ret == -1){ret = mkfifo("fifo1" , 0664);if(ret == -1){perror("mkfifo");exit(0);}}ret = access("fifo2" , F_OK);if(ret == -1){ret = mkfifo("fifo2" , 0664);if(ret == -1){perror("mkfifo");exit(0);}}// 只读开fifo1int fdr = open("fifo1" , O_RDONLY);if(fdr == -1){perror("open");exit(0);}cout<<"打开fifo1成功..."<<"等待读取..."<<endl;// 只写开fifo2int fdw = open("fifo2" , O_WRONLY);if(fdw == -1){perror("open");exit(0);}cout<<"打开fifo2成功..."<<"等待写入..."<<endl;char buf[128];// 循环写读while(1){// 读数据memset(buf , 0 , sizeof(buf));ret = read(fdr , buf , sizeof(buf));if(ret<=0){perror("read");exit(0);}cout<<"进程B读到数据 - "<<buf;memset(buf , 0 , sizeof(buf));// 获取输入数据fgets(buf , sizeof(buf) , stdin);// 写入ret = write(fdw , buf , strlen(buf));if(ret == - 1){perror("write");exit(0);}}close(fdr);close(fdw);}

内存映射

效率较高的IPC , 直接对内存进行操作;将磁盘文件的数据映射到内存(栈和堆之间的地址空间),通过修改内存来修改磁盘文件;

通过将磁盘文件映射到两个进程地址空间中实现进程间的通信;

mmap - 文件映射到内存/munmap -  解除映射

/*void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);功能: 映射一个文件到内存中参数:addr - 映射内存的首地址 - NULL 由内核指定length - 映射的数据的长度 !=0 , 建议文件长度 -> 分页整数倍获取文件长度 - stat lseekprot -  对申请的内存映射区的操作权限PROT_EXEC - 执行PROT_READ - 读权限PROT_WRITE - 写权限PROT_NONE - 没有权限要操作映射内存,必须要有读的权限PROT_READ、PROT_READ|PROT_WRITEflagsMAP_SHARED - 映射区的数据自动和磁盘文件同步(进程通信必备)MAP_PRIVATE - 映射区的数据自动和磁盘文件不同步(copy on write)fd - 磁盘文件描述符(open获得)- 文件大小!=0 , open指定权限不能和prot有冲突(open>prot)offset - 偏移量 必须是4K整数倍 一般不用;0 - 不偏移返回值:创建好的内存首地址 , 失败返回MAP_FAILED(void* -1)int munmap(void *addr, size_t length);功能:释放内存映射参数:addr - 释放内存的首地址length - 要释放的内存大小 和mmap中的length保持一致*//*使用内存映射实现进程间的通信1. 有关系的进程没有子进程时,通过父进程创建内存映射区父子进程共享创建的内存映射区2. 没关系的进程准备一个大小不是0的磁盘文件进程1 通过磁盘文件创建内存映射 - 得到操作内存指针进程2 通过磁盘文件创建内存映射 - 得到操作内存指针NOTE:内存映射区通信是非阻塞的
*/#include <iostream>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>using namespace std;int main(){//1.  打开文件int fd = open("test.txt" , O_RDWR);if(fd == -1){perror("open");exit(0);}// 获取文件大小int size = lseek(fd , 0 , SEEK_END);void* ptr = mmap(NULL , size , PROT_READ|PROT_WRITE , MAP_SHARED , fd , 0);if(ptr == MAP_FAILED){perror("mmap");exit(0);}// 创建子进程pid_t pid = fork();if(pid>0){strcpy((char*) ptr , "我是你爹");wait(NULL);}else if(pid == 0){char buf[64];strcpy(buf , (char*) ptr);cout<<"子进程读到的数据:"<<buf<<endl;}munmap(ptr , size);return 0;
}

NOTE

1.如果对mmap的返回值(ptr)做++! I(ptr++),munmap是否能够成功?

可以对ptr进行++操作;

但munmap需要传递内存的首地址,++后不能正确释放内存;


2. 如果open时O_RDONLY,mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?

错误,返回MAP_FAILED

open函数中的权限建议与prot权限保持一致,大于等于也可;


3. 如果文件偏移量为1000会怎样?

文件偏移量必须是4k的整数倍 - 错误 返回MAP_FAILED


4. mmap什么情况下会调用失败?

        - length = 0

        - prot的权限只指定了写 或 权限大于open

        ..............


5. 可以open的时候O_CREAT一个新文件来创建映射区吗?

可以!但是创建的文件大小如果为0则会出错

可以对新的文件进行拓展 - lseek/truncate


6. mmap后关闭文件描述符,对mmap映射有没有影响?

不会产生问题;映射区仍然存在 创建映射区的fd关闭没什么影响


7. 对ptr越界操作会怎样?

void* ptr = mmap(NULL , 100 ...)

4K

越界操作操作的是非法内存 ——> 段错误

通过内存映射实现文件拷贝

// 使用内存映射实现拷贝
/*思路:1. 原始文件进行映射2. 创建一个新的文件(通过拓展,保证文件不为0)3. 新文件的数据映射到内存4. 通过内存拷贝 即可实现5. 释放资源
*/#include <iostream>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>using namespace std;int main(){// 1.int fd = open("old.txt" , O_RDWR);if(fd == -1){perror("open");exit(0);}int size = lseek(fd , 0 , SEEK_END);// 2.int fdn = open("new.txt" , O_RDWR|O_CREAT , 0664);if(fdn == -1){perror("open");exit(0);}truncate("new.txt" , size);write(fdn , " " , 1);// 3.void* ptr1 = mmap(NULL , size , PROT_READ | PROT_WRITE , MAP_SHARED , fd , 0);if(ptr1 == MAP_FAILED){perror("mmap");exit(0);}void* ptr2 = mmap(NULL , size , PROT_READ | PROT_WRITE , MAP_SHARED , fdn , 0);if(ptr2 == MAP_FAILED){perror("mmap");exit(0);}// 4.memcpy(ptr2 , ptr1 , size);// 5.munmap(ptr2 , size);munmap(ptr1 , size);close(fdn);close(fd);return 0;
}

匿名映射

/*匿名映射:不需要文件实体 只能用在父子进程之间的通信- flags - MAP_ANONYMOUS fd指定为-1即可 offset为0
*/
#include <iostream>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>using namespace std;int main(){// 1. 创建匿名内存映射区int len = 4096;void* ptr = mmap(NULL , len , PROT_READ | PROT_WRITE , MAP_SHARED | MAP_ANONYMOUS , -1 , 0);if(ptr == MAP_FAILED){perror("mmap");exit(0);}// 父子进程间通信pid_t pid = fork();if(pid > 0){strcpy((char*)ptr , "hello 647");wait(NULL);}else if(pid == 0){sleep(1);cout<<(char*) ptr<<endl;}int ret = munmap(ptr , len);if(ret == -1){perror("munmap");exit(0);}return 0;
}

信号概述

信号是事件发生时对进程的通知机制 , 有时称为软件中断(软件层次上对中断机制的模拟 - 异步通信);

信号通常源于内核 ,引发内核产生信号的事件:

1. 前台进程,用户通过输入特殊中断字符发送信号;eg. Ctrl+c

2. 硬件发生异常

3. 系统状态发生变化 - alarm定时器到期引起SIGALRM

4. kill 命令

信号的特点:

1. 简单

2. 不能携带大量信息

3. 满足某个特定条件才能发送

4. 优先级较高*

查看系统定义的信号列表:kill -l;1~31为常规信号 , 其余为预定义好的实时信号

查看信号的详细信息:man 7 signal

信号的状态:产生、未决、递达

kill、raise、abort函数

Core处理动作会生成Core文件 - 保存进程异常退出的错误信息

#include <stdio.h>
#include <string.h>int main(){char* buf;strcpy(buf , "hello");return 0;
}

 要生成core文件需要先通过ulimit修改core文件的大小;

注意这里生成不了core文件可以通过sudo service apport stop关闭错误收集系统;

进gdb调试后,core-file core即可打印错误信息;

/*int kill (pid_t pid, int sig);功能:给任何进程/进程组pid,发送任何信号sig参数:pid>0 - 将信号发送给指定进程0 - 将信号发送给当前进程组所有进程-1 - 将信号发送给每一个有权限接收这个信号的进程<-1 - abs(pid)进程组sig - 信号编号/宏值,如果为0表示不发送任何信号kill(getppid() , 9);int raise (int sig);功能:给当前进程发送信号(给自己)参数:sig - 要发送的信号返回值:成功 - 0失败 - !0void abort(void);功能:发送SIGABRT给当前进程,杀死当前进程
*/
#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
using namespace std;int main(){pid_t pid = fork();if(pid == 0){int i = 0;for(i ; i<5 ; i++){cout<<"子进程"<<endl;sleep(1);}}else if(pid > 0){cout<<"父进程"<<endl;sleep(2);cout<<"杀死子进程"<<endl;kill(pid , SIGINT);}return 0;
}

alarm函数(自然定时法 , 与进程状态无关)

unsigned int alarm(unsigned int seconds);

/*/#include <unistd.h>unsigned int alarm(unsigned int seconds);功能:设置定时器(闹钟),函数调用开始倒计时,不会阻塞倒计时为0,给当前进程发SIGALRM参数:seconds - 倒计时/s , 参数为0 - 定时器无效取消一个定时器 - alarm(0)返回值 - 若之前没有定时器返回0- 之前定时器,倒计时剩余的时间SIGALRM:默认终止当前进程,某个进程都只有一个唯一定时器;
*/
#include <iostream>
#include <unistd.h>
using namespace std;int main(){int sec = alarm(5);cout<<"seconds = "<<sec<<endl;sleep(2);sec = alarm(2);cout<<"seconds = "<<sec<<endl;while(1){}return 0;
}

 实际的时间 = 内核时间 + 用户时间 + 消耗的时间(IO...)

进行文件IO操作比较浪费时间;

setitimer定时器函数(非阻塞)

/*#include <sys/time.h>int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);功能:设置定时器(闹钟) , 替代alarm函数,精度更高 - us可以实现周期定时参数: which - 定时器以什么时间计时ITINER_REAL - 真实事件->SIGALRMITIMER_VIRTUAL - 用户时间->SIGVTALRMITIMER_PROF - 用户态和内核态下的事件->SIGPROFnew_value - 设置定时器的属性struct itimerval { // 定时器的结构体struct timeval it_interval;  //Interval for periodic timer 间隔时间 struct timeval it_value;     //Time until next expiration  延迟时间};struct timeval { // 时间的结构体time_t      tv_sec;          //seconds suseconds_t tv_usec;         //microseconds };old_value - 记录上一次的时间参数 一般设为NULL 不使用返回值:成功 - 0失败 - -1 并设置errno
*/#include <iostream>
#include <unistd.h>
#include <sys/time.h>
#include <cstdio>
#include <cstdlib>
using namespace std;// 过3s 每2s定时一次
int main(){// 设置结构体struct itimerval new_value;new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL , &new_value , NULL);if(ret == -1){perror("setitimer");exit(0);}getchar();return 0;
}

捕捉signal信号 - signal/sigaction

signal会根据不同的UNIX版本而表现不同;sigaction不会,建议使用sigaction

一定要在定时器设置之前注册信号捕捉!!!

/*#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);功能:设置某个信号的捕捉行为参数:signum - 要捕捉的信号handler - 捕捉到信号的处理方法SIG_IGN - 忽略信号SIG_DFL - 使用信号默认的行为回调函数 - 由内核调用 程序员只负责写函数; 捕捉到后如何处理信号返回值:成功 - 上一次注册的信号处理函数;第一次返回NULL失败 - SIG_ERR 并设置errnoSIGKILL SIGSTOP不能被捕捉、忽略、处理*/
#include <iostream>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <cstdio>
#include <cstdlib>
using namespace std;void meet(int num){// num 表示捕捉到信号的值cout<<"逮到你了~"<<endl;
}// 过3s 每2s定时一次
int main(){// 注册信号捕捉// signal(SIGALRM,  SIG_IGN);// signal(SIGALRM,  SIG_DFL);signal(SIGALRM,  meet);// 设置结构体struct itimerval new_value;new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL , &new_value , NULL);if(ret == -1){perror("setitimer");exit(0);}getchar();return 0;
}

信号集

sigantion涉及到了信号集的概念 - 很多信号组成的集合 , 数据类型 - sigset_t;

PCB中有两个重要的信号集 - 阻塞信号集和未决信号集;两个信号集都由内核使用位图机制来实现 - 64位;不能直接对两个信号集进行操作,需要自定义另一个集合,借助信号集操作函数来对其进行操作;

/*对自定义信号集进行操作#include <signal.h>int sigemptyset(sigset_t *set);功能 - 清空信号集的数据,标志位置0参数 - 需要操作的信号集(传出参数)返回值:成功 - 0失败 - -1 并设置errnoint sigfillset(sigset_t *set);功能 - 标志位置1参数 - 需要操作的信号集(传出参数)返回值:成功 - 0失败 - -1 并设置errnoint sigaddset(sigset_t *set, int signum);功能 - 设置信号集中的某一标志位为1,阻塞该信号参数set - 需要操作的信号集(传出参数)signum - 需要阻塞的信号返回值:成功 - 0失败 - -1 并设置errnoint sigdelset(sigset_t *set, int signum);功能 - 设置信号集中的某一标志位为0,不阻塞该信号参数set - 需要操作的信号集(传出参数)signum - 需要不阻塞的信号返回值:成功 - 0失败 - -1 并设置errnoint sigismember(const sigset_t *set, int signum);功能 - 判断某个信号是否阻塞参数set - 需要操作的信号集signum - 待判断的信号返回值:被阻塞 - 1没被阻塞 - 0错误 - -1
*/
#include <iostream>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <cstdio>
#include <cstdlib>
using namespace std;int main(){sigset_t set;// 创建//清空sigemptyset(&set);int ret = sigismember(&set , SIGINT);if(ret == 0){cout<<"SIGINT 不阻塞"<<endl;}else if(ret == 1){cout<<"SIGINT 阻塞"<<endl;}// 添加信号集sigaddset(&set , SIGINT);sigaddset(&set , SIGQUIT);ret = sigismember(&set , SIGINT);if(ret == 0){cout<<"SIGINT 不阻塞"<<endl;}else if(ret == 1){cout<<"SIGINT 阻塞"<<endl;}// 信号集删除sigdelset(&set , SIGQUIT);ret = sigismember(&set , SIGQUIT);if(ret == 0){cout<<"SIGQUIT 不阻塞"<<endl;}else if(ret == 1){cout<<"SIGQUIT 阻塞"<<endl;}return 0;
}

可以通过sigprocmask对内核中的阻塞信号集进行操作

/*#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);功能:将自定义信号集中的数据设置到内核中参数:how:如何对内核阻塞信号集进行处理SIG_BLOCK - 将用户设置的阻塞信号集添加到内核中 内核中原来的数据不变(|)mask |= setSIG_UNBLOCK - 根据用户设置清除内核中阻塞信号(&)mask &= ~setSIG_SETMASK - 覆盖set - 自定义信号集oldset - 保存设置之前内存中信号集状态 , 一般NULL返回值:成功 - 0失败 - -1 (两个错误号)int sigpending(sigset_t *set);功能:获取内核中未决信号集参数:set - 传出参数
*/
// 写一个程序 不断把所有的常规信号的未决状态打印到屏幕(位)
#include <iostream>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <cstdio>
#include <cstdlib>
using namespace std;int main(){// 设置2、3信号阻塞sigset_t set;sigemptyset(&set);sigaddset(&set , SIGINT);sigaddset(&set , SIGQUIT);sigprocmask(SIG_BLOCK , &set , NULL);while(1){sigset_t pending_set;sigemptyset(&pending_set);sigpending(&pending_set);//遍历前32位for(int i = 1 ; i<=32 ; i++){if(sigismember(&pending_set , i)==1){cout<<"1";}else if(sigismember(&pending_set , i)==0){cout<<"0";}else{perror("sigismember");exit(0);}}cout<<endl;}return 0;
}

信号捕捉函数 - sigaction

信号捕捉处理过程中使用临时阻塞信号集,信号处理完后恢复内核中的阻塞信号集;

信号处理过程中,再发出信号会阻塞,且阻塞的常规信号不支持排队;

/*#include <signal.h>int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);功能:检查/改变信号的处理参数:signum - 需要捕捉的信号编号/宏值act - 捕捉到信号之后的处理动作oldact - 上次的相关设置,一般NULL返回值:成功 - 0失败 - -1struct sigaction {// 信号捕捉到后的处理函数void     (*sa_handler)(int);// 不常用void     (*sa_sigaction)(int, siginfo_t *, void *);// 临时阻塞信号集 信号捕捉函数执行过程中临时阻塞某些信号sigset_t   sa_mask;// 使用哪个处理捕捉信号 0 - sa_handler/SA_SIGINFO - sa_sigactionint        sa_flags;// 被废弃掉了 - NULLvoid     (*sa_restorer)(void);};*/
#include <iostream>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <cstdio>
#include <cstdlib>
using namespace std;void meet(int num){// num 表示捕捉到信号的值cout<<"逮到你了~"<<endl;
}// 过3s 每2s定时一次
int main(){struct sigaction act;act.sa_flags = 0;act.sa_handler = meet;sigemptyset(&act.sa_mask);// 注册信号捕捉sigaction(SIGALRM , &act , NULL);// 设置结构体struct itimerval new_value;new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL , &new_value , NULL);if(ret == -1){perror("setitimer");exit(0);}while(1){}return 0;
}

SIGCHLD信号

1. 子进程终止

2. 子进程接收到SIGSTOP - 暂停

3. 处于暂停的子进程收到SIGCONT唤醒

给父进程发送SIGCHLD,父进程默认忽略;

/*通过SIGCHLD解决僵尸进程的问题
*/
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>
#include <cstdlib>
using namespace std;void meet(int num){while(1){int ret = waitpid(-1 , NULL , WNOHANG);if(ret > 0){cout<<"子进程结束了 - "<<ret<<endl;}else if(ret == 0){break;}else{break;}}
}int main(){// 提前设置阻塞信号集 - SIGCHLDsigset_t set;sigemptyset(&set);sigaddset(&set , SIGCHLD);sigprocmask(SIG_BLOCK , &set , NULL);pid_t pid;for(int i = 0 ; i < 5 ; i++){pid = fork();if(pid == 0){break;}}if(pid > 0){// 捕捉SIGCHLD信号struct sigaction act;act.sa_flags = 0;act.sa_handler = meet;sigemptyset(&act.sa_mask);sigaction(SIGCHLD , &act , NULL);sigprocmask(SIG_UNBLOCK , &set , NULL);while(1){cout<<"父进程: "<<getpid()<<endl;sleep(2);}}else if(pid == 0){cout<<"子进程:"<<getpid()<<endl;}return 0;
}

注意:当没有子进程时,waitpid也会返回-1;并不是只有在错误得时候才返回-1;

共享内存

共享内存的效率高于内存映射;允许多个进程共享同一物理内存区域,共享内存段为用户空间的一部分,因此共享内存无需内核介入(相比其他IPC通信少);

与管道等要求发送进程将数据从用户空间的缓冲区复制进内核内存和接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种 IPC 技术的速度更快。

/*key_t ftok(const char *pathname, int proj_id);功能:根据指定路径名和int值生成一个共享内存的key参数:pathname - 路径名proj_id - int值,但系统调用只是用其中1个字节问题1:操作系统如何知道一块共享内存被多少个进程关联?- 共享内存维护了一个结构体struct shmid_ds 其中有一个成员shm_nattach记录该信息可以通过ipcs -a 查所有通信方式信息ipcs -m 共享内存;ipcrm 删除(标记删除不会释放)ipcs -q 消息队列ipcs -s 信号问题2:可不可以对共享内存进行多次删除 shmct1可以的因为shmct1 标记删除共享内存,不是直接删除,当关联进程为0才会被真正删除如果一个进程和共享内存取消关联就不能继续操作这个共享内存;问题三:共享内存和内存映射的区别1. 共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)2. 共享内存效率更高3. 所有进程操作的是统一共享内存,内存映射是每个进程在自己的虚拟地址空间有一块独立内存4. 进程突然退出,共享内存还存在,内存映射区会消失,但磁盘文件中的数据还在5. 内存映射区:进程退出就销毁共享内存:进程退出,需要手动删除(所有关联进程数为0),进程退出会自动和共享内存取消关联
*/
// write
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstdio>
#include <string.h>
using namespace std;int main(){// 1. 创建int shmid = shmget(100 , 4096 , IPC_CREAT | 0664);// 2. 关联void* ptr = shmat(shmid , NULL , 0);// 3. 写数据char* ctr=new char[20];string str = "hello 647";strcpy(ctr,str.c_str());memcpy(ptr , ctr , sizeof(str)+1);cout<<"按任意键继续"<<endl;getchar();// 4. 解除关联shmdt(ptr);// 5. 删除共享内存shmctl(shmid , IPC_RMID , NULL);return 0;
}// read
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstdio>
#include <string.h>
using namespace std;int main(){// 1. 创建int shmid = shmget(100 , 0 , IPC_CREAT);// 2. 关联void* ptr = shmat(shmid , NULL , 0);// 3. 读数据cout<<(char*)ptr<<endl;cout<<"按任意键继续"<<endl;getchar();// 4. 解除关联shmdt(ptr);// 5. 删除共享内存shmctl(shmid , IPC_RMID , NULL);return 0;
}

守护进程

终端

UNIX系统,用户通过终端登陆系统得到一个shell进程,这个终端为shell控制终端 - 保存于PCB;

默认情况下标准输入、标准输出、标准错误都指向控制终端;

进程组

进程组是一组相关进程集合,进程组的生命周期从首进程创建组开始,最后一个成员进程退出组结束;

会话

会话是一组相关进程组的集合,会话首进程为创建该会话的id,进程Id为会话id;

一个会话中的所有进程共享单个控制终端,一个终端最多可能会成为一个会话的控制终端;会话首进程为该终端的控制进程;

在任一时刻,会话中的其中一个进程组会成为终端的前台进程组,其他进程组会成为后台进程组。只有前台进程组中的进程才能从控制终端中读取输入。当用户在控制终端中输入终端字符生成信号后,该信号会被发送到前台进程组中的所有成员。

守护进程 - 后台服务进程

生命周期很长,守护进程会在系统启动的时候被创建并一直运行直至系统被关闭。

它在后台运行并且不拥有控制终端。没有控制终端确保了内核永远不会为守护进程自动生成任何控制信号以及终端相关的信号(如 SIGINT、SIGQUIT)

创建步骤

1. fork(),父进程退出 - 确保子进程不是进程组首进程/防止父进程结束后显示shell提示符

2. setsid() - 脱离控制终端

3. 清楚进程umask,确保守护进程创建文件、目录所需的权限

4. 该当前目录为根目录

5. 关继承的打开的文件描述符

6. 关闭文件描述符0 1 2后,守护进程打开/dev/null,重定向文件描述符到该设备

7. 业务逻辑

/*写一个守护进程,每隔2s获取系统时间 写入磁盘文件
*/
#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstdlib>
#include <fcntl.h>
#include <sys/time.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <sstream>
using namespace std;void work(int num){time_t tm = time(NULL);struct tm* loc = localtime(&tm);// char buf[1024];// sprintf(buf, "%d-%d-%d %d:%d:%d\n", loc->tm_year, loc->tm_mon, loc->tm_mday, loc->tm_hour, loc->tm_min, loc->tm_sec);// cout<<buf<<endl;char* str = asctime(loc);int fd = open("time.txt" , O_RDWR|O_CREAT , 0664);write(fd , str , strlen(str));
}int main(){// 创建子进程 - 防止进程组冲突pid_t pid = fork();if(pid>0){exit(0);}// 新建会话 - 脱离控制终端setsid();// 设置掩码umask(022);// 更改工作目录chdir("/home/nowcoder");// 关文件描述符int fd = open("/dev/null" , O_RDWR);dup2(fd , STDIN_FILENO);dup2(fd , STDOUT_FILENO);dup2(fd , STDERR_FILENO);// 业务逻辑struct sigaction act;act.sa_flags = 0;act.sa_handler = work;sigemptyset(&act.sa_mask);sigaction(SIGALRM , &act , NULL);struct itimerval val;val.it_interval.tv_sec = 2;val.it_interval.tv_usec = 0;val.it_value.tv_sec = 2;val.it_value.tv_usec = 0;setitimer(ITIMER_REAL , &val , NULL);while(1){sleep(10);}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/99641.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

打包个七夕exe玩玩

前段时间七夕 当别的哥们都在酒店不要不要的时候 身为程序员的我 还在单位群收到收到 正好后来看到大佬些的这个 https://www.52pojie.cn/thread-1823963-1-1.html 这个贱 我必须要犯&#xff0c;可是我也不能直接给他装个python吧 多麻烦 就这几个弹窗 好low 加上bgm 再打包成…

解决Maven依赖下载问题:从阿里云公共仓库入手

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

CSS学习笔记03

CSS笔记03 盒子模型 什么是盒子模型 概念&#xff1a; CSS 盒子模型就是在网页设计中经常用到的一种思维模型&#xff0c;是 CSS 布局的基石&#xff0c;主要规定了元素是如何显示的以及元素间的相互关系。定义所有元素都可以有像盒子一样的平面空间和外形。包含内容区、内边…

如何建设一个安全运营中心(SOC)?

然信息安全管理问题主要是个从上而下的问题&#xff0c;不能指望通过某一种工具来解决&#xff0c;但良好的安全技术基础架构能有效的推动和保障信息安全管理。随着国内行业IT应用度和信息安全管理水平的不断提高&#xff0c;企业对于安全管理的配套设施如安全运营中心&#xf…

招投标系统简介 企业电子招投标采购系统源码之电子招投标系统 —降低企业采购成本

​功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外…

【图解RabbitMQ-3】消息队列RabbitMQ介绍及核心流程

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;CSDN实力新星&#xff0c;后端开发两年经验&#xff0c;曾担任甲方技术代表&#xff0c;业余独自创办智源恩创网络科技工作室。会点点Java相关技术栈、帆软报表、低代码平台快速开…

智汇云舟亮相2023服贸会,全面展示视频孪生技术与产品

9月2日-6日&#xff0c;为期5天的2023年中国国际服务贸易交易会&#xff08;以下简称&#xff1a;服贸会&#xff09;在北京首钢园举办。在电信、计算机和信息服务专题展馆中&#xff0c;智汇云舟有幸作为北京市专精特新和数字孪生企业优秀代表受邀参展&#xff0c;并携视频孪生…

C语言入门 Day_14 for循环

前言 我们定义了一个数组以后&#xff0c;要使用&#xff08;读取或者修改&#xff09;数组元素的话&#xff0c;可以一个一个的读取&#xff0c;就前两课学的那样&#xff0c;代码类似这个结构。 int number_list[5]{1,2,3,4,5}; printf("%d\n",number_list[0]); …

Bootstrap的行、列布局设计(网络系统设计)

目录 00-基础知识01-等宽列布局02-指定某一列的宽度03-根据内容自动改变列的宽度04-五种预定义列宽度 .col、.col-sm-*、.col-md-*、.col-lg-*、.col-xl-*05-不同视口宽度按不同的分列方案划分06-删除列内容的盒模型的外边距07-超过12列怎么办&#xff1f;08-重新排列各列的顺序…

Bootstrap的标题类(标题样式h1~h6)

Bootstrap 的标题字体大小通常遵循以下样式规则&#xff1a; h1 标题的字体大小为 2.5rem&#xff08;40像素&#xff09;。h2 标题的字体大小为 2rem&#xff08;32像素&#xff09;。h3 标题的字体大小为 1.75rem&#xff08;28像素&#xff09;。h4 标题的字体大小为 1.5re…

大数据的关键技术之——大数据采集

大数据的关键技术之——大数据采集 本文目录&#xff1a; 一、写在前面的话 二、大数据采集概念 三、大数据采集步骤 3.1、大数据采集步骤&#xff08;总体角度&#xff09; 3.2、大数据采集步骤&#xff08;数据集角度&#xff09; 3.3、大数据采集步骤&#xff08;数据…

O2OA(翱途)开发平台 V8.1正式发布

尊敬的O2OA(翱途)平台合作伙伴、用户以及亲爱的开发小伙伴们&#xff0c;平台 V8.1版本已正式发布。正值8月的最后一周&#xff0c;我们以更安全、更高效、更好用的崭新面貌迎接9月的到来。 O2OA开发平台v8.1版本更注重于对系统级别的安全防护。其中重大的更新&#xff0c;是对…