进程之间通信方式

1.进程之间通信方式介绍

  • 早期UNIX进程间通信方式
    1.无名管道(pipe)
    2.有名管道(fifo)
    3.信号(signal)
    4.套接字(socket)
  • System V IPC
    1.共享内存(share memory)
    2.消息队列(message queue)
    3.信号灯集(semaphore set) [ˈseməfɔːr]
  • 其中除套接字外的通信方式适用于本地进程之间通信,而套接字更适用于不同主机通过网络通信

2.无名管道

2.1图解

2.2解释:

无名管道在内核中被创建,进程通过管道通信,进而实现数据交换

2.3特点:

1.无名管道只能用于有亲缘关系的进程之间的通信.例如父子进程,祖孙进程,兄弟进程...
2.无名管道是一种单工的通信模式,具有固定的读端与写端.即管道在某一时刻只能被一个进程读或者写.
3.无名管道在被创建时候会返回2个文件描述符,分别用于读管道和写管道

2.4函数

#include 

/*
功能:创建无名管道
参数:一个整型数组,保存文件描述符
返回值:成功返回0,失败返回-1并设置errno
*/
int pipe(int pipefd[2]);

注意:
1.数组第0个元素保存读管道,第1个元素保存写管道
2.由于无名管道在创建之后无文件路径,在文件系统中不可见,仅仅在内存中存在,如果其他进程需要访问无名管道则需要继承创建管道的进程.所以无名管道只能用于有亲缘关系的进程之间通信
3.无名管道被父进程创建后被子进程继承,同时管道返回2个文件描述符,存放于Fd[0](读)和Fd[1](写).由于是单工通信,故一个进程只能使用一个文件描述符,此处父进程只能读管道,子进程只能写数据

无名管道单向通信

4.要想实现无名管道双向数据通信就得创建两个管道
无名管道双向数据通信

2.5举例

子进程1和子进程2分别往管道中写入字符串:父进程读管道内容并打印

#include 
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    pid_t pid1, pid2;
    char buf[32];
    int pfd[2];
    //先创建无名管道
    if (pipe(pfd) < 0)
    {
        perror("pipe");
        exit(-1);
    }
    //再创建子进程1
    if ((pid1 = fork()) < 0)
    {
        perror("fork1");
        exit(-1);
    }
    else if (pid1 == 0){    //子进程1
        strcpy(buf, "I am process1");
        write(pfd[1], buf, 32);
        exit(0);
    }
    else{
        //创建子进程2
        if ((pid2 = fork()) < 0){
            perror("fork2");
            exit(-1);   
        }
        else if(pid2 == 0){ //子进程2
            sleep(1);
            strcpy(buf, "I am process2");
            write(pfd[1], buf, 32);
            exit(0);    
        }
        else{
            wait(NULL); //回收子进程1
            read(pfd[0], buf, 32);
            printf("%s\n", buf);
            wait(NULL); //回收子进程2
            read(pfd[0], buf, 32);
            printf("%s\n", buf);
        }
    }
    return 0;
}

运行结果:

hanqi@hanqi-PC:~/C/thread$ gcc noname_pipe.c 
hanqi@hanqi-PC:~/C/thread$ ./a.out
I am process1
I am process2

其中两条语句打印时间间隔为1s
注意:
1.管道与文件虽然都通过文件描述符读取或者写入数据,但是文件读取数据后,被读取的数据仍然存在于文件内,而管道数据被读取后数据将不存在于管道里了
2.无名管道内数据不可定位

2.6读无名管道

  • 当写端存在时候去读管道情况:
    当无名管道被创建后会返回2个文件描述符,一个用于读管道,一个用于写管道.故当前进程有1个读端,1个写端.如果当前进程创建了一个子进程,那么子进程会继承父进程的文件描述符,故当前无名管道有2个读端与2个写端.写端存在表示至少有一个进程可以通过文件描述符写管道
    1.情况1:
    a.管道内有数据,此时当管道内存在的数据比读管道指定读取数据多时(例如管道内有50Byte,进程需要读取20Byte),此时read函数返回实际读取的字节数20Byte
    b.管道内有数据,此时当管道内存在的数据比读管道指定读取数据少时(例如管道内有20Byte,进程需要读取50Byte),此时read函数返回实际读取的字节数20Byte
    2.情况2:
    管道内无数据时进程去读取管道数据,此时进程会读阻塞,直到管道中又存在数据
  • 当写端不存在时候去读管道情况:
    a.管道内有数据,此时当管道内存在的数据比读管道指定读取数据多时(例如管道内有50Byte,进程需要读取20Byte),此时read函数返回实际读取的字节数20Byte
    b.管道内无数据,read()会直接返回0,不会发生阻塞.可用于判断发数据方数据是否发送完毕.例如有一个进程发数据,一个进程接受数据,当发送方发送数据完毕后会关闭发送进程,同时也关闭了管道的写端,此时接受进程会读取数据,当read()函数返回0时候,表明发送方发送数据完毕
  • 图解:


2.7写无名管道

  • 当读端存在时候去写管道情况:
    读端存在表示至少有一个进程可以通过文件描述符读管道
    1.情况1:
    a.管道有足够的空间(1024Byte),进程写入(256Byte)数据,write()函数返回写入数据大小(256)
    b.管道空间不足(剩余200Byte),进程写入(256Byte),系统不保证原子操作,即先写入200Byte,剩余56Byte等管道有空间时继续写入.此时进程会写阻塞,直到管道有空间之后继续写入剩余数据.利用此特性可计算无名管道大小.

计算无名管道大小步骤:
1.循环向管道内写入数据,每次写入1024字节,直到写阻塞
2.统计循环次数

#include 
#include 
#include 

int main(void)
{
    int count = 0;
    char buf[1024];
    pid_t pid;
    int pfd[2];
    if (pipe(pfd) < 0)
    {
        perror("pipe");
        exit(-1);
    }
    while(1){
        write(pfd[1], buf, 1024);
        count++;
        printf("have write %dkb\n", count);
    }
    return 0;
}

执行结果如下:

hanqi@hanqi-PC:~/C/thread$ gcc get_pipe_size.c 
hanqi@hanqi-PC:~/C/thread$ ./a.out 
have write 1kb
have write 2kb
...
have write 63kb
have write 64kb

由此可见本机无名管道大小为64k字节

  • 当读端不存在时候去写管道情况:
    无论管道是否有足够空间,当读端不存在时候向管道写入数据会使程序异常退出,此可进程被信号结束,即管道断裂

验证管道断裂
1.子进程写管道
2.父进程回收

#include 
#include 
#include 
#include 

int main(void)
{
    pid_t pid;
    int pfd[2];
    int status;
    char buf[32];
    if (pipe(pfd) < 0){
        perror("pip");
        exit(-1);
    }
    close(pfd[0]);//直接关闭读端
    if ((pid = fork()) < 0){
        perror("fork");
        exit(-1);
    }
    else if (pid == 0){
        write(pfd[1], buf, 32);
        exit(0);
    }
    else{
        wait(&status);
        printf("status = %x\n", status);
    }
    return 0;
}

执行结果如下:

hanqi@hanqi-PC:~/C/thread$ gcc pipe_broken.c 
hanqi@hanqi-PC:~/C/thread$ ./a.out 
status = d
hanqi@hanqi-PC:~/C/thread$ 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    

子进程返回值位解析参考:https://www.jianshu.com/p/3395dbba8c16
可以看到,进程被信号结束,并返回status = d,其中d的二进制为1101,由于status只有低16位有效,而此刻bit0~bit6为000 1101不等于0,表明进程被信号结束,且信号类型为d(十进制为13),通过kill -l可知,13信号表示SIGPIPE,即管道信号

假如注释掉close(pfd[0]);语句,那么线程将会正常退出,status将等于子线程中exit(x)中x的高8位,例如exit(77),那么status = 0x4D00,其中高8位即77

3.有名管道

3.1有名管道特点

  • 对应管道文件,可用于任意进程之间的通信(无名管道仅局限于有亲缘关系的进程之间通信)
  • 打开管道时候可以指定读写方式.以读方式打开,则返回的是读端,写端与读写端同理
  • 打开管道返回的是文件描述符,所以可通过文件IO操作,将内容存放在内存中
  • 不论什么管道,当关闭读写端都关闭时候,管道存放在内存中的内容都将被释放
  • 只有当有名管道读端和写端同时存在的时候open才不会阻塞
  • 当写端存在时候,读端会阻塞

3.2函数

#include 
#include 
#include 
/*
功能:创建一个有名管道
参数:
参数1:创建的有名管道的路径
参数2:管道文件的权限,如0666
返回值:成果返回0,失败返回-1
*/
int mkfifo(const char *path, mode_t mode);

3.3实例

  • 功能:创建2个进程,进程A负责循环从键盘中输入并写入有名管道myfifo,输入quit时候退出;进程B负责循环统计进程A每次写入myfifo的字符串长度
  • 代码:
    1.有名管道创建:
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    if(mkfifo("myfifo", 0666) < 0)
    {
        perror("mkfifo");
        exit(-1);
    }
    printf("create myfifo success\n");
    return 0;
}

2.进程A,写管道:

#include 
#include 
#include 
#include 
#include 

#define N 32
int main(void)
{
    int pfd;
    char buff[N];
    if((pfd = open("myfifo", O_WRONLY)) < 0)
    {
        perror("open");
        exit(-1);
    }
    printf("myfifo is opened\n");
    while (1)
    {
        fgets(buff, N, stdin);
        if(strcmp(buff, "quit\n") == 0)
        {
            break;
        }
        write(pfd, buff, N);
    }
    close(pfd);
    return 0;    
}

3.进程B,读管道:

#include 
#include 
#include 
#include 
#include 

#define N 32

int main(void)
{
    int pfd;
    char buff[N];

    if((pfd = open("myfifo", O_RDONLY)) < 0)
    {
        perror("open");
        exit(-1);
    }
    printf("myfifo is opened\n");
    while (read(pfd, buff, N) > 0)
    {
        printf("the length of string is %d\n", strlen(buff));
    }
    close(pfd);
    return 0;
}
  • 编译过程如下,当执行创建管道程序时候,在本地文件夹下出现了myfifo文件,文件类型为p(管道文件),且大小为0,注意:有名管道文件大小始终为0,因为其存在于内存中,而非磁盘中:
hanqi@hanqi-PC:~/C/process/fifo$ gcc -o create_fifo create_fifo.c 
hanqi@hanqi-PC:~/C/process/fifo$ gcc -o read_fifo read_fifo.c 
hanqi@hanqi-PC:~/C/process/fifo$ gcc -o write_fifo write_fifo.c 
hanqi@hanqi-PC:~/C/process/fifo$ ./create_fifo 
create myfifo success
hanqi@hanqi-PC:~/C/process/fifo$ ls -l
总用量 48
-rwxr-xr-x 1 hanqi hanqi 8800 7月  23 16:45 create_fifo
-rw-r--r-- 1 hanqi hanqi  263 7月  23 16:44 create_fifo.c
prw-r--r-- 1 hanqi hanqi    0 7月  23 16:46 myfifo
-rwxr-xr-x 1 hanqi hanqi 9000 7月  23 16:45 read_fifo
-rw-r--r-- 1 hanqi hanqi  435 7月  23 16:35 read_fifo.c
-rwxr-xr-x 1 hanqi hanqi 9048 7月  23 16:46 write_fifo
-rw-r--r-- 1 hanqi hanqi  493 7月  23 16:42 write_fifo.c
  • 运行结果如下:

1.当只执行读程序或者只执行写程序时候,结果如下,可以看到没有任何输出,说明程序在open处阻塞,因为只有当有名管道读端和写端同时存在的时候open才不会阻塞,当只存在二者之一的时候会阻塞

@hanqi-PC:~/C/process/fifo$ ./read_fifo 

2.开启2个终端,当开启读写程序后现象如下:
读终端:

hanqi@hanqi-PC:~/C/process/fifo$ ./read_fifo 
myfifo is opened

写终端:

hanqi@hanqi-PC:~/C/process/fifo$ ./write_fifo 
myfifo is opened

3.最终现象:当在读终端内输入字符后,写终端会打印出字符长度信息
读终端:

hanqi@hanqi-PC:~/C/process/fifo$ ./read_fifo 
myfifo is opened
the length of string is 6
the length of string is 2
hanqi@hanqi-PC:~/C/process/fifo$ 

写终端:

hanqi@hanqi-PC:~/C/process/fifo$ ./write_fifo 
myfifo is opened
hello
a
quit
hanqi@hanqi-PC:~/C/process/fifo$ 

4.信号

4.1信号机制

  • 信号是在软件层上对中断机制的一种模拟,是一种异步通信方式
  • linux内核通过信号通知用户进程,不同的信号类型代表不同的事件
  • linux对早期unix信号机制进行了扩展
  • 通过kill -l查看系统信号种类.前31种为早期unix系统信号,称为不可靠信号(进程可能对信号做出错误的反应以及信号可能丢失),后面的则为linux针对不可靠信号做出的改进
hanqi@hanqi-PC:~/C/thread$ 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    
  • 进程对信号有不同的响应方式(缺省方式,忽略信号,捕捉信号(即当系统接收到不同类型信号后会做出不同响应,类似中断回调函数))

4.2常用信号

  • 如下表
信号名 含义 对进程默认操作
SIGHUP 该信号在用户终端关闭时候产生,通常是发给和该终端关联的会话内所有进程 终止
SIGINT 该信号在用户键入INTR字符(Ctrl+C)时产生,内核发送此信号送到当前终端的所有前台进程 终止
SIGQUIT 该信号与SIGINT类似,但由QUIT字符(通常是Ctrl+)来产生 终止
SIGILL 该信号在一个进程企图执行一条非法指令时产生 终止
SIGSEGV 该信号在非法访问内存时产生,如野指针,缓冲区溢出,数组越界等 终止
SIGPIPE 当进程往一个没有读端的管道中写入时产生,代表管道断裂 终止
SIGKILL 该信号用于结束进程,并不能被捕捉和忽略 终止
SIGSTOP 该信号用于暂停进程,并不能被捕捉和忽略 暂停进程
SIGTSTP 该信号用于暂停进程,用户可键入SUSP字符(通常为Ctrl+Z)发出信号 暂停进程
SIGCONT 该信号让进程进入运行状态 继续运行
SIGALRM 该信号用于通知进程定时器时间已到 终止
SIGUSR1/2 该信号保留给用户程序使用 终止

4.3信号相关命令

  • kill [-signal] pid命令,用于向进程发送指定类型信号.默认发送SIGTERM信号用于结束进程.
    1.-signal为信号类型
    2.pid可指定发送对象,即进程号,如kill -9 4410.kill不光可以向进程发信号,还可以向进程组发信号,如kill -9 -4410,即结束该进程组下的所有进程.kill -9 -1表示向系统所有进程(除init进程和进程本身)发送结束进程的信号,此举十分危险,慎用!!!
  • killall [-u user | prog]命令,根据进程运行时候的程序名或者用户发送信号,如killall a.out,向系统中所有运行a.out程序的进程发信号.killall -u hanqi,向系统中所有由用户hanqi创建的进程发信号,root用户慎用!!!

4.4信号相关函数

  • 信号发送(不带消息)
#include 
#include 

/*
功能:向一个进程或者进程组发送指定信号
参数:
参数1:进程号,0代表发送给同组进程,-1代表所有进程(除init进程和进程本身)
参数2:信号类型,通过宏指定
返回值:成功返回0,失败返回-1
*/
int kill(pid_t pid, int sig);
/*
功能:向当前进程发送进程号
参数:信号类型,通过宏指定
返回值:成功返回0,失败返回-1
*/
int raise(int sig);
  • 定时器,在linux中,一个进程只能有一个定时器
#include 
#include 

/*
功能:设置定时器
参数:定时时间间隔,为0时候取消定时器,单位:秒
返回值:成功返回上一个定时器的剩余时间,失败返回-1
*/
int alarm(unsigned int seconds);
  • pause
#include 
#include 

/*
功能:让进程进入阻塞状态,直到被信号中断
参数:无
返回值:被信号中断后返回-1,errno为EINTR
*/
int pause(void);
  • 设置信号响应方式
#include 
#include 

/*
功能:设置信号响应方式
参数:
参数1:要设置的信号类型
参数2:函数指针,信号处理函数:SIG_DFL代表缺省方式,SIG_IGN代表忽略信号
返回值:成功时候返回原来的信号处理函数,失败返回SIG_ERR
*/
void (*signal(int signo, void (*handler)(int)))(int);
  • 发送信号(带消息)
#include 

/*
功能:向进程发送信号,可以传递用户参数到信号处理函数中
参数:
参数1:指定接收信号的进程ID
参数2:确定即将发送的信号
参数3:一个联合数据结构union sigval,指定了信号传递的参数
返回值:失败返回-1
*/
int sigqueue(pid_t pid, int sig, const union sigval value);

union sigval {
               int   sival_int;
               void *sival_ptr;
           };
  • 捕捉信号(带消息)
#include 

/*
功能:捕获信号,并对信号做更复杂的处理
参数:
参数1:要捕获的信号类型
参数2:新的信号处理方式
参数3:先前信号的处理方式
返回值:失败返回-1
*/
int sigaction(int signum, const struct sigaction *act,
              struct sigaction *oldact);

struct sigaction {
               void  (*sa_handler)(int);/*和signal()的参数handler相同,代表新的
                          信号处理函数或者SIG_DFL(缺省方式处理),SIG_IGN(忽略信号).
                          只有其指定为函数的时候才会对sa_mask和sa_flags字段加以处理*/
               void  (*sa_sigaction)(int, siginfo_t *, void *);/*如果设置了
                          SA_SIGINFO标志位,则会使用sa_sigaction处理函数,否则使用sa_handler处理函数*/
               sigset_t  sa_mask;/*定义一组信号,在调用由sa_handler所定义的处理器程序时
                          将阻塞该组信号,不允许它们中断此处理程序的执行*/
               int  sa_flags;/*位掩码,指定用于控制信号处理过程的各种选项*/
                             /*SA_NODEFER:捕获该信号时,不会在执行处理器程序时将该信号自动添加到进程掩码中*/
                             /*SA_ONSTACK:针对此信号调用处理器函数时,使用了由sigaltstack()安装的备选栈*/
                             /*SA_RESETHAND:当捕获该信号时,会在调用处理器函数之前将信号处置重置为默认值(即SIG_IGN)*/
                             /*SA_SIGINFO:调用的信号处理函数携带额外参数,其中提供了关于信号的深入信息*/
               void  (*sa_restorer)(void);
           };

4.5实例

  • 简单定时器
#include 
#include 
#include 
#include 

int main(void)
{
  alarm(3);
  pause();//此处让进程睡眠,否则进制直接结束
  printf("I have been waken up\n");
  return 0;
}

运行结果:

hanqi@hanqi-PC:~/C/process$ ./a.out 
Alarm clock
hanqi@hanqi-PC:~/C/process$ 

解释:当3s时间到达后内核会发送闹钟信号,但闹钟信号默认执行的操作是结束进程,故不会执行alarm(3)之后的语句就直接结束进程.alarm经常用于网络超时检测

  • 信号捕捉
#include 
#include 
#include 
#include 

void handler(int signo)
{
    if(signo == SIGINT)
    {
        printf("I have got SIGINT\n");
    }else if (signo == SIGQUIT)
    {
        printf("I have got SIGQUIT\n");
    }
    
}
int main(void)
{
    signal(SIGINT, handler);//设置信号响应方式
    //signal(SIGINT, SIG_IGN);/*直接忽略信号*/
    signal(SIGQUIT, handler);
    while(1);
    pause();
    return 0;
}

运行结果:

hanqi@hanqi-PC:~/C/process$ ./a.out 
^CI have got SIGINT

解释:当程序运行时候会一直捕捉信号,按下Ctrl+C时候产生SIGINT信号,故打印

  • 发送信号(不带参数)
#include 
#include 
#include 
#include 

int main(int argc, char * argv[])
{
    int pid;
    int signum;
    int kill_res;
    signum = atoi(argv[1]);/*输入信号类型*/
    pid = atoi(argv[2]);/*输入进程号*/
    printf("signum=%d,pid=%d\n", signum, pid);
    if((kill_res = kill(pid, signum)) < 0)
    {
        perror("kill");
    }
    return 0;
}

运行结果:

hanqi@hanqi-PC:~/C/IPC$ ps aux|grep test
hanqi     10202  101  0.0   4048   648 pts/2    R+   16:51   0:23 ./test
hanqi     10206  0.0  0.0  14536   944 pts/1    S+   16:51   0:00 grep test
hanqi@hanqi-PC:~/C/IPC$ ./a.out 9 10202
signum=9,pid=10202

hanqi@hanqi-PC:~/C/IPC$ ./test
已杀死

解释:此处使用函数向test死循环程序发送一个结束进程的信号(9),进而杀死test进程
注意:通过终端传参输入的参数为char类型,需要将其转换为int类型后才能给kill函数使用,使用atoi函数进行类型转换

  • 发送信号(带参数)
/*发送方*/
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    int signum = atoi(argv[1]);/*终端获取要发送的信号类型*/
    int pid = atoi(argv[2]);/*终端获取接收方的进程号*/
    union sigval value;/*联合体,做sigquue函数参数*/
    value.sival_int = 100;/*发送的参数,此处发送数字100*/
    sigqueue(pid, signum, value);
    printf("send pid = %d\n", getpid());
    printf("send done\n");
    return 0;
}
/*接收方*/
#include 
#include 
#include 
#include 

void handler(int signum, siginfo_t *info, void *context)
{
    printf("get signum %d\n", signum);
    if(context != NULL)
    {
        printf("get data=%d\n", info->si_int);
        printf("get data=%d\n", info->si_value.sival_int);
        printf("from which pid = %d\n", info->si_pid);
    }
}
int main()
{
    printf("rcv pid = %d\n", getpid());
    struct sigaction act;/*结构体,做sigaction函数参数*/
    act.sa_sigaction = handler;/*指定带参数的信号服务函数,需设置SA_SIGINFO标志位*/
    act.sa_flags = SA_SIGINFO;/*表示信号服务函数带参数*/
    sigaction(SIGUSR1, &act, NULL);
    while (1);
    return 0;
}

运行结果:

/*发送方*/
hanqi@hanqi-PC:~/C/IPC$ ./s_send 10 12244
send pid = 12510
haha
send done


/*接收方*/
get signum 10
get data=100
get data=100
from which pid = 12510

5.System V IPC对象

5.1特点

  • IPC对象包含:共享内存,消息队列和信号灯集
  • 每个IPC对象都有唯一的ID,在创建IPC对象创建时分配
  • IPC对象被创建后会一直存在,直到被显式地删除,管道则当读写端不存在时候会被释放
  • 每个IPC对象有一个关联的KEY,通过KEY可以让不同进程使用同一个IPC对象,KEY=0表示该IPC对象为私有对象,即只提供给当前进程使用IPC对象
  • ipcs/ipccrm命令,查看IPC对象/删除IPC对象
hanqi@hanqi-PC:~/Desktop$ ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 1507328    hanqi      600        524288     2          dest         
0x00000000 1343489    hanqi      600        234952     2          dest         
0x00000000 491522     hanqi      600        4196352    2          dest         
0x00000000 1310723    hanqi      600        234952     2          dest         

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     

5.2使用流程

  • 先创建KEY,再通过KEY去打开或者创建IPC通道,最后访问IPC通道

5.3函数

  • ftok函数创建KEY
#include 
#include 

/*
功能:创建唯一KEY值
参数:
参数1:文件路径,该路径存在且可被访问
参数2:用于生成key数字,不能为0,且低8位有效
返回值:成功返回合法的key值,失败返回-1
*/
key_t ftok(const char *path, int proj_id);

注意:该函数原理是将取出path参数(目的是根据文件索引节点在系统内的唯一性来取一个数值),再与proj_id(0~255)进行移位组合得到一个key值

5.4实例

  • ftok使用(创建key)
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    key_t key;
    if((key = ftok(".", 'a')) == -1)
    {
        perror("key");
        exit(-1);
    }
}

注意:当ftok函数参数不一样时候,其所得key值也不同

6.共享内存

6.1共享内存特点

  • 共享内存是一种最为高效的进程之间的通信方式,进程可以直接读写内存,而不需要任何的数据拷贝
  • 共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
  • 由于多个进程可同时访问共享内存,因此需要进程同步与互斥机制配合

6.2共享内存使用步骤

  • 创建/打开共享内存
  • 映射共享内存,即将指定的共享内存映射到进程的地址空间用于访问
  • 读写共享内存
  • 撤销共享内存映射
  • 删除共享内存对象

6.3函数

  • shmget(共享内存创建)
#include 
#include 

/*
功能:创建/打开共享内存对象
参数:
参数1:与共享内存关联的key,可为IPC_PRIVATE(0)或者ftok()生成
参数2:共享内存大小,单位:字节
参数3:共享内存标志位,为IPC_CREAT或者0666,创建或者设置权限
返回值:成功返回共享内存ID,失败返回-1
*/
int shmget(key_t key, int size, int shmflg);
  • shmat(共享内存映射)
#include 
#include 

/*
功能:实现共享内存映射
参数:
参数1:共享内存id,即shmget()返回值
参数2:指针,即指定映射后的地址,NULL表示系统自动映射
参数3:标志位,指定当前进程对共享内存的读写权限,0表示可读写,SHM_RDONLY表示只读
返回值:成功返回映射后的地址,失败返回(void *)-1
*/
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 共享内存读写:通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型
  • shmdt(共享内存撤销映射)
#include 
#include 

/*
功能:共享内存撤销映射,同free
参数:共享内存首地址
返回值:成功返回0,失败返回-1
*/
int shmdt(void *shmaddr);

注意:不使用共享内存时候应及时撤销映射,即使在进程结束时候会自动撤销

  • shmctl(共享内存控制)
#include 
#include 

/*
功能:对共享内存进行设置
参数:
参数1:共享内存id
参数2:要执行的操作,如IPC_STAT(获取共享内存属性),IPC_SET(设置共享内存属性),
      IPC_RMID(删除共享内存ID)等
参数3:存放共享内存属性信息的结构体,当设置/获取共享内存时设置,其他时候传入NULL
返回值:成功返回0,失败返回-1
*/
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

6.4实例

  • 创建共享内存:创建一个私有共享内存,大小为512字节,权限为0666
#include 
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    int shmid;
    if((shmid = shmget(IPC_PRIVATE, 512, 0666)) < 0)
    {
        perror("shmget");
        exit(-1);
    }
}
  • 创建共享内存:创建/打开一个和key关联的共享内存,大小为1024字节,权限为0666
#include 
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    int shmid;
    key_t key;
    if((key = ftok(".", 'm')) == -1)
    {
        perror("ftok");
        exit(-1);
    }
    if((shmid = shmget(key, 1024, IPC_CREAT|0666)) < 0)
    {
        perror("shmget");
        exit(-1);
    }
}

注意:当创建的共享内存为私有的时候不用加IPC_CREAT,因为私有的共享内存一定是新建的,当创建的为公有共享内存时候,需指定IPC_CREAT,即当执行shmget函数时候,系统会检查:如果与key关联的共享内存对象不存在时候会创建共享内存对象,并与key关联,如果存在则直接返回ID.

  • 共享内存读写:在共享内存中存放键盘输入的字符串
#include 
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    char *addr;
    int shmid;
    key_t key;
    if((key = ftok(".", 'm')) == -1)
    {
        perror("ftok");
        exit(-1);
    }
    if((shmid = shmget(key, 1024, IPC_CREAT|0666)) < 0)
    {
        perror("shmget");
        exit(-1);
    }
    if((addr = (char *)shmat(shmid, NULL, 0)) == (char *)-1)
    {
        perror("shmat");
        exit(-1);
    }
    fgets(addr, 1024, stdin);
}

6.5共享内存使用注意事项

  • 共享内存大小是有限的,使用ipcs -l可查看共享内存大小.在/proc/sys/kernel/shmmax路径下可查看大小,使用sysctrl命令可修改
hanqi@hanqi-PC:~/Desktop$ ipcs -l

------ Messages Limits --------
max queues system wide = 32000
max size of message (bytes) = 8192
default max size of queue (bytes) = 16384

------ Shared Memory Limits --------
max number of segments = 4096
max seg size (kbytes) = 18014398509465599
max total shared memory (kbytes) = 18014398442373116
min seg size (bytes) = 1

------ Semaphore Limits --------
max number of arrays = 32000
max semaphores per array = 32000
max semaphores system wide = 1024000000
max ops per semop call = 500
semaphore max value = 32767
  • 查看共享内存指令ipcs -m
  • 删除共享内存指令ipcrm -m shmid
  • 共享内存会被多个进程使用,故其会被第一个使用到的进程创建,被最后使用的进程删除shmctl(shmid, IPC_RMID, NULL),调用此函数的时候共享内存并没有被删除,而仅仅被打上了删除的标签,真正删除是要等所有进程都取消对共享内存的映射且被标上删除标签时候,其中共享内存每被映射一次,系统中的nattach就会加1,直到nattach = 0时候才会删除

7.消息队列

7.1概念

  • 消息队列是System V IPC对象的一种
  • 消息队列由消息队列ID来唯一标识
  • 消息队列即一个存放消息的列表.用户可在消息队列中添加消息,读取消息等
  • 消息队列可以按照类型来发送/接收消息

7.2消息队列结构(链表形式存放)

7.3消息队列使用步骤

  • 打开/创建消息队列 msgget
  • 向消息队列发送消息 msgsnd
  • 从消息队列中接收消息 msgrcv
  • 控制消息队列 msgctl

7.4函数

  • msgget(创建/打开消息队列)
#include 
#include 
#include 

/*
功能:
参数:
参数1:与消息队列关联的key,为IPC_PRIVATE(私有消息队列)或ftok()创建
参数2:标志位,一般为IPC_CREAT|0666
返回值:成功返回消息队列ID,失败返回-1
*/
int msgget(key_t key, int msgflg);
  • msgsnd(消息发送)
#include 
#include 
#include 

/*
功能:发送消息
参数:
参数1:消息队列id
参数2:消息缓冲区地址
参数3:消息正文长度
参数4:标志位0(当消息队列无空间时候会阻塞)或IPC_NOWAIT()(不会阻塞,立刻返回)
返回值:成功返回0,失败返回-1
*/
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • msgrcv(消息接收)
#include 
#include 
#include 
/*
功能:消息接收
参数:
参数1:消息队列id
参数2:存放接收消息的缓冲区地址
参数3:指定接收消息的长度
参数4:指定接收的消息类型
参数5:标志位0(当消息队列无空间时候会阻塞,直到消息队列中有指定消息,消息队列被删除,被信号打断)
      或IPC_NOWAIT()(不会阻塞,立刻返回)
返回值:成功返回接收到的消息长度,失败返回-1
*/
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • msgctl(控制消息队列)
#include 
#include 
#include 

/*
功能:控制消息队列
参数:
参数1:消息队列id
参数2:要执行的操作,IPC_STAT(获取消息队列属性),IPC_SET(设置获取消息队列的属性),
      IPC_RMID(删除获取消息队列属性ID)等
参数3:存放消息队列属性信息的结构体,当设置/获取消息队列时设置,其他时候传入NULL
返回值:成功返回0,失败返回-1
返回值:成功返回0,失败返回-1
*/
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

7.5消息格式

  • 通信双方首先定义好统一的消息格式
  • 用户根据应用需求定义结构体类型
  • 结构体首成员类型为long,代表消息类型(正整数)
  • 其他成员都属于消息正文
  • 举例
typedef struct
{
    long mtype;
    char buff[64];
}MSG;
#define LEN (sizeof(MSG) - sizeof(long))  //消息正文长度

7.6实例

  • 消息队列创建/打开
#include 
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    int msgid;
    key_t key;
    if((key = ftok(".", 'q')) == -1)
    {
        perror("ftok");
        exit(-1);
    }
    if((msgid = msgget(key, IPC_CREAT|0666)) < 0)
    {
        perror("msgget");
        exit(-1);
    }
}
  • 消息队列发送
#include 
#include 
#include 
#include 
#include 

typedef struct 
{
    long stype;
    char buff[64];
}MSG;
#define LEN (sizeof(MSG) - sizeof(long))

int main(void)
{
    int msgid;
    key_t key;
    MSG my_msg;
    if((key = ftok(".", 'm')) == -1)
    {
        perror("ftok");
        exit(-1);
    }
    if((msgid = msgget(key, IPC_CREAT|0666)) < 0)
    {
        perror("msgget");
        exit(-1);
    }
    /*消息类型 */
    my_msg.stype = 100;
    fgets(my_msg.buff, 64, stdin);
    msgsnd(msgid, &my_msg, LEN, 0);
}
  • 接收消息
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct 
{
    long stype;
    char buff[64];
}MSG;
#define LEN (sizeof(MSG) - sizeof(long))

int main(void)
{
    int msgid;
    key_t key;
    MSG rec_msg;
    if((key = ftok(".", 'm')) == -1)
    {
        perror("ftok");
        exit(-1);
    }
    if((msgid = msgget(key, IPC_CREAT|0666)) < 0)
    {
        perror("msgget");
        exit(-1);
    }
    if(msgrcv(msgid, &rec_msg, LEN, 200, 0) < 0)
    {
        perror("msgrcv");
        exit(-1);
    }
}
  • 两个进程通过消息队列轮流将键盘输入的字符串发送给对方,接受并打印对方发送的消息
    A端程序:
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct 
{
    long mtype;
    char msg_buff[64];
}MSG;

#define LEN (sizeof(MSG) - sizeof(long))
#define TYPEA 100
#define TYPEB 200

int main(void)
{
    key_t key;
    int msgid;
    MSG msg;
    if((key = ftok(".", 'q')) == -1)
    {
        perror("ftok");
        exit(-1);
    }
    if((msgid = msgget(key, IPC_CREAT|0666)) < 0)
    {
        perror("msgget");
        exit(-1);
    }
    while (1)
    {
        msg.mtype = TYPEB;
        printf("input->");
        fgets(msg.msg_buff, 64, stdin);
        msgsnd(msgid, &msg, LEN, 0);
        /*如果发送的消息为quit,则退出进程 */
        if(strcmp(msg.msg_buff, "quit\n") == 0)
        {
            break;
        }
        if(msgrcv(msgid, &msg, LEN, TYPEA, 0) < 0)
        {
            perror("msgrcv");
            exit(-1);
        }
        /*如果接收的消息为quit,则删除消息队列 */
        if(strcmp(msg.msg_buff, "quit\n") == 0)
        {
            msgctl(msgid, IPC_RMID, 0);
            exit(0);
        }
        printf("rec from B:%s", msg.msg_buff);
    }
    return 0;
}

B端程序:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct 
{
    long mtype;
    char msg_buff[64];
}MSG;

#define LEN (sizeof(MSG) - sizeof(long))
#define TYPEA 100
#define TYPEB 200

int main(void)
{
    key_t key;
    int msgid;
    MSG msg;
    if((key = ftok(".", 'q')) == -1)
    {
        perror("ftok");
        exit(-1);
    }
    if((msgid = msgget(key, IPC_CREAT|0666)) < 0)
    {
        perror("msgget");
        exit(-1);
    }
    while (1)
    {
        if(msgrcv(msgid, &msg, LEN, TYPEB, 0) < 0)
        {
            perror("msgrcv");
            exit(-1);
        }
        if(strcmp(msg.msg_buff, "quit\n") == 0)
        {
            msgctl(msgid, IPC_RMID, 0);
            exit(0);
        }
        printf("rec from A:%s", msg.msg_buff);        
        msg.mtype = TYPEA;
        printf("input->");
        fgets(msg.msg_buff, 64, stdin);
        msgsnd(msgid, &msg, LEN, 0);
        if(strcmp(msg.msg_buff, "quit\n") == 0)
        {
            break;
        }
    }
    return 0;
}

A端运行效果:

hanqi@hanqi-PC:~/C/IPC$ ./A
input->hello,B
rec from B:hello,A
input->quit
hanqi@hanqi-PC:~/C/IPC$ 

B端运行效果:

hanqi@hanqi-PC:~/C/IPC$ ./B
rec from A:hello,B
input->hello,A
hanqi@hanqi-PC:~/C/IPC$ 

注意:使用ipcs -q命令可查看当前消息队列

hanqi@hanqi-PC:~/C/IPC$ ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x710103fb 0          hanqi      666        192          3      

之后Ctrl+C结束进程A和B,但是消息队列仍然存在,即单纯Ctrl+C结束进程是无法删除消息队列,需要在代码中显式删除.

不足:此示例只能实现一方发送一方接收,即要么进入接受阻塞,要么进入发送阻塞,导致不能在任意时刻发送与接收,故可使用多进程/线程实现同时收发消息或者引入服务器端,实现不同通信方式

改进后使用多线程,创建2个线程,一个发送,一个接收
A端程序:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct 
{
    long mtype;
    char msg_buff[64];
}MSG;

#define LEN (sizeof(MSG) - sizeof(long))
#define TYPEA 100
#define TYPEB 200
pthread_mutex_t lock;
void *receive_msg(void *arg);
int msgid;
MSG msgs, msgr;
int main(void)
{
    pthread_t a_thread;
    key_t key;
    if((key = ftok(".", 'q')) == -1)
    {
        perror("ftok");
        exit(-1);
    }
    if((msgid = msgget(key, IPC_CREAT|0666)) < 0)
    {
        perror("msgget");
        exit(-1);
    }
    if(pthread_mutex_init(&lock, NULL) != 0)
    {
        printf("fail to init pthread_nutex_init\n");
        exit(-1);
    }
    if(pthread_create(&a_thread, NULL, receive_msg, NULL) != 0)
    {
        perror("pthread_create");
        exit(-1);
    }
    while (1)
    {
        msgs.mtype = TYPEB;
        //pthread_mutex_lock(&lock);
        printf("input->");
        
        fgets(msgs.msg_buff, 64, stdin);
        msgsnd(msgid, &msgs, LEN, 0);
        if(strcmp(msgs.msg_buff, "quit\n") == 0)
        {
            break;
        }
        //pthread_mutex_unlock(&lock);
    }

    return 0;
}
void *receive_msg(void *arg)
{
    //puts("pthread A");
    while(1)
    {
        //pthread_mutex_lock(&lock);
        if(msgrcv(msgid, &msgr, LEN, TYPEA, 0) < 0)
        {
            perror("msgrcv");
            exit(-1);
        }
        if(strcmp(msgr.msg_buff, "quit\n") == 0)
        {
            msgctl(msgid, IPC_RMID, 0);
            pthread_exit("end");
        }
        printf("rec from B:%s", msgr.msg_buff);
        //pthread_mutex_unlock(&lock);
    }
    return NULL;
}

B端程序:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct 
{
    long mtype;
    char msg_buff[64];
}MSG;

#define LEN (sizeof(MSG) - sizeof(long))
#define TYPEA 100
#define TYPEB 200

pthread_mutex_t lock;
void *receive_msg(void *arg);
int msgid;
MSG msgs, msgr;
int main(void)
{
    pthread_t a_thread;
    key_t key;
    if((key = ftok(".", 'q')) == -1)
    {
        perror("ftok");
        exit(-1);
    }
    if((msgid = msgget(key, IPC_CREAT|0666)) < 0)
    {
        perror("msgget");
        exit(-1);
    }
    if(pthread_mutex_init(&lock, NULL) != 0)
    {
        printf("fail to init pthread_nutex_init\n");
        exit(-1);
    }
    if(pthread_create(&a_thread, NULL, receive_msg, NULL) != 0)
    {
        perror("pthread_create");
        exit(-1);
    }
    while (1)
    {
        msgs.mtype = TYPEA;
        //pthread_mutex_lock(&lock);
        printf("input->");
        
        fgets(msgs.msg_buff, 64, stdin);
        msgsnd(msgid, &msgs, LEN, 0);
        if(strcmp(msgs.msg_buff, "quit\n") == 0)
        {
            break;
        }
        //pthread_mutex_unlock(&lock);
    }
    return 0;
}
void *receive_msg(void *arg)
{
    //puts("pthread B");
    while(1)
    {
        //pthread_mutex_lock(&lock);
        if(msgrcv(msgid, &msgr, LEN, TYPEB, 0) < 0)
        {
            perror("msgrcv");
            exit(-1);
        }
        if(strcmp(msgr.msg_buff, "quit\n") == 0)
        {
            msgctl(msgid, IPC_RMID, 0);
            pthread_exit("end");
        }
        printf("rec from A:%s", msgr.msg_buff);
        //pthread_mutex_unlock(&lock);
    }
    return NULL;
}

7.7消息队列使用注意事项

  • 消息队列删除时候与共享内存不同,只要进程执行msgctl(msgmid, IPC_RMID, NULL)时候,消息队列就会被立刻删除

8.信号灯机制

8.1简介与特点

  • 信号灯也叫信号量,用于进程/线程同步或者互斥的机制
  • 信号灯类型:
    1.Posix无名信号灯,用于一个进程内部线程之间的同步或者互斥.参见:https://www.jianshu.com/p/b2b6f5b119a7
    2.Posix有名信号灯,用于进程之间的同步与互斥
    3.System V信号灯
  • Posix信号灯代表一类资源(计数信号灯),其值通常代表某类资源数量,而System V的信号灯是一个或者多个计数信号灯的集合
  • 可同时操作集合中的多个信号灯,进而避免了申请多个资源时出现死锁

死锁:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
例如:有进程1和进程2,他们都需要对资源A与B执行操作.在进程1中先对资源A执行P操作,之后对资源B执行P操作,进程2反之.假设当进程1对资源A执行P操作同时,进程2对资源B执行了P操作,那么当进程A再对资源B执行P操作时会阻塞,同理进程2对资源A执行P操作也会进入阻塞,最终导致两个进程阻塞.使用System V信号灯集合则可以同时申请资源A和B,进而避免了死锁

8.2信号灯使用步骤

  • 打开/创建信号灯 semget
  • 初始化信号灯 semctl
  • P/V操作 semop
  • 删除信号灯 semctl

8.3函数

  • semget(打开/创建信号灯)
#include 
#include 
#include 

/*
功能:打开/创建信号灯
参数:
参数1:与信号灯关联的key,为IPC_PRIVATE(私有信号灯)或ftok()创建
参数2:集合中信号灯个数
参数3:标志位,一般为IPC_CREAT|0666,或者IPC_EXCL(用于检查信号灯是否重复创建,重复创建则
     会出错)
返回值:成功返回信号灯id,失败返回-1
*/
int semget(key_t key, int nsems, int semflg);

使用示例:

if((semicl = semget(key, 3, IPC_CREAT|0666|IPC_EXCL)) < 0)
{
    /*如果信号灯被创建则重新打开 */
    if(errno == EEXIST)
    {
        semicl = semget(key, 3, 0666);
    }
}else
{
    初始化信号灯;
}
  • semctl(信号灯初始化)
#include 
#include 
#include 

/*
功能:初始化信号灯,只能初始化1次
参数:
参数1:要操作的信号灯集id
参数2:集合中信号灯编号(从0开始)
参数3:指定执行的操作,SETVAL IPC_RMID
参数4:当参数3为SETVAL时候,参数4为共用体类型
返回值:成功返回0,失败返回-1
*/
int semctl(int semid, int semnum, int cmd, ...);

使用示例,假设信号灯集合里有2个信号灯,第一个初始化为2,第二个初始化为0

union semun {
  int              val;    /* Value for SETVAL */
  struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
  unsigned short  *array;  /* Array for GETALL, SETALL */
  struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */
};

union semun myun;
myun.val = 2;
if(semctl(semid, 0, SETVAL, myun) < 0)
{
    perror("semctl");
    exit(-1);
}
myun.val = 0;
if(semctl(semid, 1, SETVAL, myun) < 0)
{
    perror("semctl");
    exit(-1);
}
  • semop(P/V操作)
#include 
#include 
#include 

/*
功能:P/V操作
参数:
参数1:要操作的信号灯集合id
参数2;描述对信号灯操作的结构体(数组)
参数3:要操作的信号灯个数
返回值:成功返回0,失败返回-1
*/
int semop(int semid, struct sembuf *sops, size_t nsops);

sembuf结构体:

struct sembuf
{
    short semnum;/*指定信号灯编号*/
    short sem_op;/*指定要执行的操作,负数(-1)P操作,正数(1)V操作*/
    short sem_flg;/*操作方式,0/IPC_NOWAIT*/
};

使用示例:对集合中信号0与信号2执行P操作

struct sembuf
{
    short semnum;
    short sem_op;
    short sem_flg;
};
int main()
{
    struct sembuf buf[3];
    /*设置第一个执行P操作的信号 */
    buf[0].semnum = 0;  /*信号0 */
    buf[0].sem_op = -1;
    buf[0].sem_flg = 0;
    /*设置第二个执行P操作的信号 */
    buf[1].semnum = 2;  /*信号2 */
    buf[1].sem_op = -1;
    buf[1].sem_flg = 0;
    semop(semicl, buf, 2);
}

8.4信号灯集/共享内存示例

  • 父子进程通过System V信号灯同步对共享内存的读写,父进程从键盘输入字符到共享内存,子进程删除字符串中的空格并打印,父进程输入quit后删除共享内存与信号灯集,程序结束
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define N 64    /*共享内存大小 */
#define READ 0  /*可读信号灯编号 */
#define WRITE 1 /*可写信号灯编号 */
/*信号灯共用体,信号灯初始化时候使用*/
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo  *__buf;
};

/**
*功能:信号灯集初始化,初始化为可写不可读
*参数:
*参数1:信号灯集id
*参数2:数组,用于记录信号灯编号
*参数3:信号灯个数
*返回值:无
*/
void init_sem(int semid, int s[], int n)
{
    int i;
    union semun myun;
    for(i = 0; i");
            fgets(shmaddr, N, stdin);
            if(strcmp(shmaddr, "quit\n") == 0)
            {
                break;
            }
            pv(semid, READ, 1);
        }
        /*回收子进程 */
        kill(pid, SIGUSR1);
    }
    
    
_error2:
    semctl(semid, 0, IPC_RMID);
_error1:
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}

执行结果如下:

hanqi@hanqi-PC:~/C/IPC$ ./a.out 
input->hello w o rld
helloworld
input->quit
hanqi@hanqi-PC:~/C/IPC$ 

你可能感兴趣的:(进程之间通信方式)