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.mzph.cn/news/65612.shtml

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

相关文章

go语言 go mod生成

1. go hello world 创建文件夹gotest&#xff0c;在其中创建test1.go文件&#xff0c;并写入 package mainimport ("fmt" )func main() {fmt.Println("hello world") } 运行命令 go run test1.go 可以看到输出hello world 2. cli 命令行的使用 代码如下…

机器人中的数值优化(六)—— 线搜索最速下降法

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

重新理解百度智能云:写在大模型开放后的24小时

在这些回答背后共同折射出的一个现实是——大模型不再是一个单选题&#xff0c;而更是一个综合题。在这个新的时代帆船上&#xff0c;产品、服务、安全、开放等全部都需要成为必需品&#xff0c;甚至是从企业的落地层面来看&#xff0c;这些更是刚需品。 作者| 皮爷 出品|产…

c语言每日一练(13)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;上学期间将看学业情况更新。 五道选择题&#xff1a; 1、程序运行的结果…

Go死码消除

概念: 死码消除(dead code elimination, DCE) 是一种编译器优化技术, 作用是在编译阶段去掉对程序运行结果没有任何影响的代码 和 逃逸分析[1],内联优化[2]并称为 Go编译器执行的三个重要优化 效果: 对于 const.go代码如下: package mainimport "fmt"func max(a, b i…

一篇文章教会你如何编写一个简单的Shell脚本

文章目录 简单Shell脚本编写1. 简单脚本编写2. Shell脚本参数2.1 Shell脚本参数判断2.1.1 文件测试语句2.1.2 逻辑测试语句2.1.3 整数值测试语句2.1.4 字符串比较语句 3. Shell流程控制语句3.1 if 条件测试语句3.1.1 if...3.1.2 if...else...3.1.3 if...elif...else 4. Shell脚…

汽车自适应巡航系统控制策略研究

目 录 第一章 绪论 .............................................................................................................................. 1 1.1 研究背景及意义 ..........................................................................................…

文件夹中lib,dll含义

.dll文件是动态链接库&#xff08;Dynamic Link Library&#xff09;的缩写&#xff0c;它包含了一组可执行的函数和数据&#xff0c;供程序调用。它可以被多个应用程序共享和重用&#xff0c;减少了代码的冗余。通过动态链接库&#xff0c;可以实现代码的模块化和提高代码的复…

ELK安装、部署、调试(五)filebeat的安装与配置

1.介绍 logstash 也可以收集日志&#xff0c;但是数据量大时太消耗系统新能。而filebeat是轻量级的&#xff0c;占用系统资源极少。 Filebeat 由两个主要组件组成&#xff1a;harvester 和 prospector。 采集器 harvester 的主要职责是读取单个文件的内容。读取每个文件&…

机器学习技术(六)——有监督学习算法之线性回归算法实操

机器学习技术&#xff08;五&#xff09;——有监督学习之线性回归算法实操 引言&#xff1a; 机器学习监督算法是一种基于已有标记数据的学习方法&#xff0c;通过对已知输入和输出数据的学习&#xff0c;建立一个模型来预测新的输入数据的输出。这种算法模仿人类的学习过程&a…

安防监控/视频汇聚平台EasyCVR调用rtsp地址返回的IP不正确是什么原因?

安防监控/云存储/磁盘阵列存储/视频汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有GB28181、RTSP/Onvif、RTMP等&#xff0c;以及厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等&#xff0c;能对外分发RTSP、RT…

docker 笔记2 Docker镜像和数据卷

参考&#xff1a; 1.镜像是什么&#xff1f;&#xff08;面试题&#xff09; 是一种轻量级、可执行的独立软件包&#xff0c;它包含运行某个软件所需的所有内容&#xff0c;我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文…

算法笔记——路径问题

在引入介绍如何写一个算法的时候&#xff0c;我们先引入一个题作为例子 1137. 第 N 个泰波那契数 - 力扣&#xff08;LeetCode&#xff09; 作为刚开始学习算法的我们&#xff0c;看到这个题目的时候&#xff0c;应该想好以下的问题&#xff1a; 1.状态表示 我们要用什么来表…

Windows7安装SSH客户端的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

SQL sever中库管理

目录 一、创建数据库 1.1库界面方式 1.2SQL命令方式 二、修改数据库 2.1库界面方式 2.2SQL命令方式 三、删除数据库 3.1库界面方式 3.2SQL命令方式 四、附加和分离数据库 4.1附加和分离数据库概述 4.2作用 4.3附加和分离数据库方法 4.4示例 一、创建数据库 1.1库…

第 3 章 栈和队列 (循环队列)

1. 背景说明 和顺序栈相类似&#xff0c;在队列的顺序存储结构中&#xff0c;除了用一组地址连续的存储单元依次存放从队列头到队列尾的元素之外&#xff0c; 尚需附设两个指针 front 和 rear 分别指示队列头元素及队列尾元素的位置。约定&#xff1a;初始化建空队列时&#x…

XmlDocument.SelectNodes 不起作用

今天采用Xpath读取Xml节点&#xff0c;怎么都读不出。 问题分析&#xff1a; 错误代码如下&#xff1a; XmlDocument xmlD new XmlDocument();xmlD.PreserveWhitespace true;xmlD.LoadXml(xStr);xmlD.SelectNodes("job-scheduling-data/schedule/job");经排查 do…

Python学习笔记——从面试题出发学习Python

Python学习笔记——从面试题出发学习Python Python学习笔记——从面试题出发学习Python1. 可变数据类型与不可变数据类型&#xff0c;深拷贝与浅拷贝&#xff0c;函数参数的传递机制1.1 变量与对象1.2 可变数据类型与不可变数据类型1.3 深拷贝与浅拷贝1.4 函数参数的传递机制1.…

Matlab(画图初阶)

目录 1.plot()函数 2. hold(添加新绘图是否保留旧绘图) 3. Plot Style 3.1 线型 3.2 标记 3.3 颜色 ​编辑 4. legend() 5.X 、Y and Title&#xff1f; 6. Text()和annotation() 7.line(创建基本线条) 7.1 基本语法 7.2 指定线条属性 7.3 更改线条属性 8.图像属性 8.1 …

c++入门一

参考&#xff1a;https://www.learncpp.com/cpp-tutorial/ When you finish, you will not only know how to program in C, you will know how NOT to program in C, which is arguably as important. Tired or unhappy programmers make mistakes, and debugging code tends…