网站html地图怎么做的,做网站哪些比较好,怎么做阿里巴巴网站,宣城网站优化进程间的通信
进程是一个独立的资源分配单元#xff0c;不能在一个进程中直接访问另一个进程的资源#xff1b;
进程间通信#xff08;IPC#xff09;的目的#xff1a;
1. 数据传输 - A进程发送数据给B进程
2. 通知事件 - eg. 进程终止通知父进程
3. 资源共享 - 多个… 进程间的通信
进程是一个独立的资源分配单元不能在一个进程中直接访问另一个进程的资源
进程间通信IPC的目的
1. 数据传输 - A进程发送数据给B进程
2. 通知事件 - eg. 进程终止通知父进程
3. 资源共享 - 多个进程之间共享资源需要内核提供互斥和同步机制
4. 进程控制 - 进程控制另一个进程的执行 匿名管道管道
UNIX系统IPC的最古老形式所有UNIX系统都支持这种通信机制
ls | wc -l : 统计一个目录中的文件数目| - 管道符
管道的特点
1.是一个内核内存中维护的缓冲器存储能力有限不同操作系统大小不同
2. 匿名管道没有文件实体有名管道有文件实体但不存储数据可以按照操作文件的方式对管道进行操作
3. 一个管道是一个字节流使用管道不存在消息/消息边界的概念不管写入数据块多大可以读任意大小的数据块
4. 先进先出
5. 管道是半双工的数据传递方向是单向的
6. 读数据是一次性操作读了就没了并且无法随机访问数据
7. 匿名管道只能在有亲缘关系的进程之间进行通信 管道的数据结构 - 循环队列 /*#include unistd.hint 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(pid0){cout我是你爹endl;while(1){char buf[1024];int len read(pipefd[0] , buf , sizeof(buf));cout父进程 getpid()读到了: bufendl;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()读到了: bufendl;}}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){coutbuf;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 ; i100 ; i){char buf[1024];sprintf(buf , hello 647 : %d , i);coutbufendl;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;}coutget: bufendl;}close(fd);return 0;
}
一个为只读而打开一个管道的进程会阻塞直到有写的权限打开管道反之同理
读管道 管道中有数据read返回实际读到的字节数 管道无数据 写端全关闭read返回0 写端没有全关闭read阻塞
写管道 读端全关闭异常终止SIGPIPE 读端没有全关闭 管道满了阻塞 没满正常写入即可返回实际写入字节数 有名管道实现聊天功能
/*实现进程A、B的聊天功能需要两个管道 一个A读B写 一个B读A写进程A1. 只写打开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(ret0){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(ret0){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 writefd - 磁盘文件描述符open获得- 文件大小!0 open指定权限不能和prot有冲突openprotoffset - 偏移量 必须是4K整数倍 一般不用0 - 不偏移返回值创建好的内存首地址 失败返回MAP_FAILEDvoid* -1int 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.husing 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(pid0){strcpy((char*) ptr , 我是你爹);wait(NULL);}else if(pid 0){char buf[64];strcpy(buf , (char*) ptr);cout子进程读到的数据bufendl;}munmap(ptr , size);return 0;
}
NOTE
1.如果对mmap的返回值(ptr)做! I(ptr)munmap是否能够成功?
可以对ptr进行操作
但munmap需要传递内存的首地址后不能正确释放内存 2. 如果open时O_RDONLYmmap时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. 创建一个新的文件通过拓展保证文件不为03. 新文件的数据映射到内存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.husing 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.husing 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*) ptrendl;}int ret munmap(ptr , len);if(ret -1){perror(munmap);exit(0);}return 0;
} 信号概述
信号是事件发生时对进程的通知机制 有时称为软件中断软件层次上对中断机制的模拟 - 异步通信
信号通常源于内核 引发内核产生信号的事件
1. 前台进程用户通过输入特殊中断字符发送信号eg. Ctrlc
2. 硬件发生异常
3. 系统状态发生变化 - alarm定时器到期引起SIGALRM
4. kill 命令 信号的特点
1. 简单
2. 不能携带大量信息
3. 满足某个特定条件才能发送
4. 优先级较高* 查看系统定义的信号列表kill -l1~31为常规信号 其余为预定义好的实时信号 查看信号的详细信息man 7 signal
信号的状态产生、未决、递达 kill、raise、abort函数
Core处理动作会生成Core文件 - 保存进程异常退出的错误信息
#include stdio.h
#include string.hint 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参数pid0 - 将信号发送给指定进程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 ; i5 ; 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.hunsigned 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);coutseconds secendl;sleep(2);sec alarm(2);coutseconds secendl;while(1){}return 0;
} 实际的时间 内核时间 用户时间 消耗的时间IO...
进行文件IO操作比较浪费时间 setitimer定时器函数非阻塞
/*#include sys/time.hint 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.htypedef 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.hint 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){coutSIGINT 不阻塞endl;}else if(ret 1){coutSIGINT 阻塞endl;}// 添加信号集sigaddset(set , SIGINT);sigaddset(set , SIGQUIT);ret sigismember(set , SIGINT);if(ret 0){coutSIGINT 不阻塞endl;}else if(ret 1){coutSIGINT 阻塞endl;}// 信号集删除sigdelset(set , SIGQUIT);ret sigismember(set , SIGQUIT);if(ret 0){coutSIGQUIT 不阻塞endl;}else if(ret 1){coutSIGQUIT 阻塞endl;}return 0;
}
可以通过sigprocmask对内核中的阻塞信号集进行操作
/*#include signal.hint 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 ; i32 ; i){if(sigismember(pending_set , i)1){cout1;}else if(sigismember(pending_set , i)0){cout0;}else{perror(sigismember);exit(0);}}coutendl;}return 0;
} 信号捕捉函数 - sigaction 信号捕捉处理过程中使用临时阻塞信号集信号处理完后恢复内核中的阻塞信号集
信号处理过程中再发出信号会阻塞且阻塞的常规信号不支持排队
/*#include signal.hint 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子进程结束了 - retendl;}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* ctrnew 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*)ptrendl;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);// coutbufendl;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(pid0){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;
}