LEVEL5day5-进程间通信方式-无名管道,有名管道,信号

1. 进程间通信介绍

早期UNIX进程间通信方式
无名管道(pipe)
有名管道 (fifo)
信号(signal)

System V IPC
共享内存(share memory)
消息队列(message queue)
信号灯集(semaphore set)

套接字(socket)

2.无名管道

2.1 无名管道特点

1.只能用于具有亲缘关系的进程之间的通信
2.单工的通信模式,具有固定的读端和写端
3.无名管道创建时会返回两个文件描述符,分别用于读写管道

2.2 无名管道相关函数

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;
}

2.3 无名管道的读写特性 (可参考 man pipe 中的范例)

读无名管道时,写端存在:    有数据:直接读取数据
                          无数据:阻塞
             写端不存在:  有数据:没有写端不存在数据       //close(pfd[1]);
                          无数据:立即返回

写无名管道时,读端存在:    有空间:写入成功
                          无空间,阻塞
             读端不存在:  无论有无空间,管道破裂,返回错误代码       //close(pfd[0]);

2.4 无名管道的大小

无名管道的底层存储也是一段缓冲区,既然是缓冲区就一定有固定大小,经过测试得出结论是64KB.

3. 有名管道

3.1 有名管道特点

对应管道文件,可用于任意进程之间进行通信
打开管道时可指定读写方式
通过文件IO操作,内容存放在内存中

3.2 头文件

#include  <unistd.h>
 #include <fcntl.h>

3.3 有名管道相关函数

1. 创建有名管道
//成功时返回0,失败时返回EOF
//path  创建的管道文件路径
//mode 管道文件的权限,如0666
int  mkfifo(const char *path, mode_t mode); 

3.4 有名管道特点

有名管道打开时有可能会阻塞

3.5 范例

/*
    有名管道,写函数
*/
#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;
}

4. 底层及内核实现

参考文档: https://blog.csdn.net/ty_laurel/article/details/72812159

5. 信号机制

信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式.

linux内核通过信号通知用户进程,不同的信号类型代表不同的事件.

Linux对早期的unix信号机制进行了扩展.

在linux下有很多信号,按可靠性分为可靠信号和非可靠信号,按时间分为实时信号和非实时信号,linux进程也有三种方式来处理收到的信号:
(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;

(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;

(3)执行缺省操作,Linux对每种信号都规定了默认操作。

6. 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进程对实时信号的缺省反应是进程终止。需要理解各种信号的缘由和正确的处理方式如下:

    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. 非法文件访问


7. 信号发送相关命令 kill / killall

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 信号类型

8. 定时器

int alarm(unsigned int seconds);
成功时返回上个定时器的剩余时间,失败时返回EOF
seconds 定时器的时间
一个进程中只能设定一个定时器,时间到时产生SIGALRM

int pause(void);
进程一直阻塞,直到被信号中断
被信号中断后返回-1,errno为EINTR

9. 设置信号响应方式 – signal

#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 代表忽略信号;  

10. signal 函数使用范例

//范例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;
}

11. 参考文献及延申:

<sigel.c>

Linux信号signal用法详解及注意事项:https://www.cnblogs.com/wudymand/p/9226438.html

Linux 高级编程 - 信号 Signal:https://dlonng.com/posts/signal  其中有关于signal再内核部分实现的 未能仔细研读 留个备份