早期UNIX进程间通信方式
无名管道(pipe)
有名管道 (fifo)
信号(signal)
System V IPC
共享内存(share memory)
消息队列(message queue)
信号灯集(semaphore set)
套接字(socket)
1.只能用于具有亲缘关系的进程之间的通信 2.单工的通信模式,具有固定的读端和写端 3.无名管道创建时会返回两个文件描述符,分别用于读写管道
1.头文件
#include <unistd.h>
2.创建无名管道 成功时返回0,失败时返回EOF;
int pipe(int pfd[2]); //pfd 包含两个元素的整形数组,用来保存文件描述符pfd[0]用于读管道;pfd[1]用于写管道
3.范例
#include <cstdio> #include <unistd.h> #include<string.h> /* 无名管道读写范例,父程写,子进程读。 */ int main() { int pfd[2],i; pid_t pid; char botton[64] = "Hello,world!"; ssize_t num; i = pipe(pfd); if (i != 0) { perror("pipe"); return -1; } pid = fork(); if (pid < 0) { perror("fork"); return -1; } else if (pid == 0) { close(pfd[1]); while (1) { memset(botton, 0, 64); read(pfd[0], botton, 63); printf("%s\n", botton); } } else { close(pfd[0]); while (1){ num = write(pfd[1], botton, 64); if (num <= 0) { perror("write"); break; } sleep(1); } } getchar(); return 0; }
读无名管道时,写端存在: 有数据:直接读取数据 无数据:阻塞 写端不存在: 有数据:没有写端不存在数据 //close(pfd[1]); 无数据:立即返回 写无名管道时,读端存在: 有空间:写入成功 无空间,阻塞 读端不存在: 无论有无空间,管道破裂,返回错误代码 //close(pfd[0]);
无名管道的底层存储也是一段缓冲区,既然是缓冲区就一定有固定大小,经过测试得出结论是64KB.
对应管道文件,可用于任意进程之间进行通信 打开管道时可指定读写方式 通过文件IO操作,内容存放在内存中
#include <unistd.h> #include <fcntl.h>
1. 创建有名管道
//成功时返回0,失败时返回EOF //path 创建的管道文件路径 //mode 管道文件的权限,如0666 int mkfifo(const char *path, mode_t mode);
有名管道打开时有可能会阻塞
/* 有名管道,写函数 */ #include <cstdio> #include <unistd.h> #include <fcntl.h> #include<sys/stat.h> int main() { int fd; char botton[64] = "Hello,world!"; unlink("./myfifo"); int re = mkfifo("./test1", 0666); if (re != 0) { perror("mkfifo"); return(-1); } fd = open("./test1", O_WRONLY); if (fd < 0) { perror("open"); return(-1); } re = write(fd,botton,63); if (re <= 0) { perror("write"); return -1; } return 0; } /* 有名管道,读函数 */ #include <cstdio> #include <unistd.h> #include <fcntl.h> #include<sys/stat.h> int main() { int fd,re; char botton[64] = "Hello,world!"; fd = open("./test1", O_RDONLY); if (fd < 0) { perror("open"); return(-1); } re = read(fd,botton,63); if (re <= 0) { perror("write"); return -1; } printf("%s\n",botton); return 0; }
参考文档: https://blog.csdn.net/ty_laurel/article/details/72812159
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式.
linux内核通过信号通知用户进程,不同的信号类型代表不同的事件.
Linux对早期的unix信号机制进行了扩展.
在linux下有很多信号,按可靠性分为可靠信号和非可靠信号,按时间分为实时信号和非实时信号,linux进程也有三种方式来处理收到的信号:
(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;
(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数; (3)执行缺省操作,Linux对每种信号都规定了默认操作。
linux@ubuntu:~$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
Linux进程对实时信号的缺省反应是进程终止。需要理解各种信号的缘由和正确的处理方式如下:
SIGHUP和控制台操作有关,当控制台被关闭时系统会向拥有控制台sessionID的所有进程发送HUP信号,默认HUP信号的action是 exit,如果远程登陆启动某个服务进程并在程序运行时关闭连接的话会导致服务进程退出,所以一般服务进程都会用nohup工具启动(该命令就是让忽略该信号)或写成一个 daemon(利用setsid进行)。
以下五组可以放在一块类比
SIGINT 终止进程,通常我们的Ctrl+C就发送的这个消息。
SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl- \ )来控制. 进程收到该消息退出时会产生core文件。
SIGKILL 消息编号为9,我们经常用kill -9来杀死进程发送的就是这个消息,程序收到这个消息立即终止,这个消息不能被捕获,封锁或这忽略,所以是杀死进程的终极武器。
SIGTERM 是不带参数时kill默认发送的信号,默认是杀死进程。
SIGINT SIGTERM区别
前者与字符ctrl+c关联,后者没有任何控制字符关联。
前者只能结束前台进程,后者则不是。
SIGTERM SIGKILL的区别
前者可以被阻塞、处理和忽略,但是后者不可以。
KILL命令的默认不带参数发送的信号就是SIGTERM.让程序有好的退出。
SIGTERM比较友好,进程能捕捉这个信号,根据您的需要来关闭程序。在某些情况下,假如进程正在进行作业而且不能中断,那么进程可以忽略这个SIGTERM信号。
因为SIGTERM可以被阻塞,所以有的进程不能被结束时,需要用kill发送SIGKILL。即:kill-9 进程号。
SIGSTOP 停止进程的执行,同SIGKILL一样不可以被应用程序所处理,注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行.由 Ctrl+Z 控制,用户可以使用使用fg/bg操作恢复执行前台或后台的进程。fg命令在前台恢复执行被挂起的进程,此时可以使用ctrl-z再次挂起该进程,bg命令在后台恢复执行被挂起的进程,而此时将无法使用ctrl-z再次挂起该进程。
SIGCONT 当SIGSTOP发送到一个进程时,通常的行为是暂停该进程的当前状态。如果发送SIGCONT信号,该进程将仅恢复执行。除了其他目的,SIGSTOP和SIGCONT用于Unix shell中的作业控制,无法捕获或忽略SIGCONT信号。
SIGPIPE 这个是向一个没有读进程的管道写数据产生的错误,这种解释过于官方。在网络编程中这个信号发生在如果客户端已经关闭了套接字, 而服务器调用了一次write,服务器就会收到一个RST segment,如果服务器再次调用write,这个时候就会产生SIGPIPE信号,系统默认的处理方式是关掉这个进程, 但是对于一个高可用的服务器程序来说,需要手动处理这个信号,所以你会看到许多服务器程序代码会在前面显式加上signal (SIGPIPE, SIG_IGN)来忽略这个信号。
SIGCHILD 这个同样是高性能服务器需要关注的信号,如果服务器采用fork产生的子进程推出后要调用wait进行资源回收,防止僵尸进程的产生,但是如果程序对子进程退出后的状态不感兴趣的话可以调用signal(SIGCHLD,SIG_IGN); 交给系统init去回收。子进程也不会产生僵尸进程了。
SIGSEGV 就是SegmentFault 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据,官方举得三个例子是:
1. buffer overflow --- usually caused by a pointer reference out of range. 野指针
2. stack overflow --- please keep in mind that the default stack size is 8192K. 栈溢出
3. illegal file access --- file operations are forbidden on our judge system. 非法文件访问
SIGBUS 指针所对应的地址是有效地址,但总线不能正常使用该指针。通常是未对齐的数据访问所致。试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域,或者以前有文件内容对应,现在为另一进程截断过的内存区域。apache爆出的一个bug就是mmap调用的时候文件发生改变就会爆出这个错误,具体参考这篇精彩的博客是 : 由mmap引发的SIGBUS http://blog.csdn.net/ctthuangcheng/article/details/8916015 SIGBUS意味着我们是在内存映射区内访问,但是已超出了底层支撑对象的大小。SIGSEGV则意味着我i们在内存映射区以外访问。
SIGURG I/O紧急信号,也就是tcp传输带外数据时使用,但是tcp手册 RFC6093中已经不建议使用紧急指针了,所以这个信号也就没什么用了。
SIGIO 当描述符上可以进行I/O时产生这个信号,这时五大IO模型中信号驱动IO模型的实现信号。
SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间.alarm函数使用该信号.
kill [-signal] pid
默认发送SIGTERM
-sig 可指定信号
pid 指定发送对象
killall [-u user | prog]
prog 指定进程名
user 指定用户名
#include <unistd.h> #include <signal.h> int kill(pid_t pid, int sig); int raise(int sig); //给自己发信号 成功时返回0,失败时返回EOF pid 接收进程的进程号: 0代表同组进程; -1代表所有进程 sig 信号类型
int alarm(unsigned int seconds);
成功时返回上个定时器的剩余时间,失败时返回EOF
seconds 定时器的时间
一个进程中只能设定一个定时器,时间到时产生SIGALRM
int pause(void);
进程一直阻塞,直到被信号中断
被信号中断后返回-1,errno为EINTR
#include <unistd.h> #include <signal.h> void (*signal(int signo, void (*handler)(int)))(int); typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 成功时返回原先的信号处理函数,失败时返回SIG_ERR signo 要设置的信号类型 handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;
//范例1, #include <cstdio> #include <unistd.h> #include <signal.h> void handler(int signo) { if (signo == SIGINT) printf("Recall SIGINT\n"); if (signo == SIGHUP) printf("Recall SIGHUP\n"); if (signo == SIGALRM); //printf("Recall SIGALRM\n"); } void myalarm(int sec) { alarm(sec); pause(); } int main() { signal(SIGINT,handler); signal(SIGHUP, handler); signal(SIGALRM, handler); while (1) { myalarm(1); } return 0; }
//范例2, #include <cstdio> #include <unistd.h> #include <signal.h> int i = 0; __sighandler_t Befor_SigFunc; void handler(int signo) { if (signo == SIGINT) { i++; printf("Recall SIGINT\n"); if (i == 2) { //signal(SIGINT, Befor_SigFunc); signal(SIGINT, SIG_DFL); //可以直接这么写,SIG_DFL表示缺省. } } else if (signo == SIGHUP) printf("Recall SIGHUP\n"); else if (signo == SIGALRM); //printf("Recall SIGALRM\n"); } void myalarm(int sec) { alarm(sec); pause(); } int main() { Befor_SigFunc = signal(SIGINT, handler); signal(SIGHUP, handler); signal(SIGALRM, handler); while (1) { myalarm(1); } return 0; }
//范例3. #include <cstdio> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> void handler(int signo) { if (signo == SIGCHLD) { printf("Recall SIGINT\n"); wait(NULL); } else if (signo == SIGALRM); } void myalarm(int sec) { alarm(sec); pause(); } int main() { signal(SIGCHLD, handler); signal(SIGALRM, handler); pid_t pid= fork(); if (pid < 0) { perror("fork"); } else if (pid == 0) { myalarm(10); } else if (pid > 0) { while (1) { myalarm(1); } } return 0; }
<sigel.c> Linux信号signal用法详解及注意事项:https://www.cnblogs.com/wudymand/p/9226438.html Linux 高级编程 - 信号 Signal:https://dlonng.com/posts/signal 其中有关于signal再内核部分实现的 未能仔细研读 留个备份