Linux进阶-进程间通信(ipc)

进程间通信:数据传输、资源共享、事件通知、进程控制。

目录

Linux系统下的ipc

无名管道(只支持具有亲缘关系的进程间通信)

有名管道(FIFO)

软中断信号(signal,信号):通知进程发生了异步事件

signal函数

kill函数:向其它进程或者线程发送信号

raise函数:向当前进程发送信号(自己给自己发信号)

信号集处理函数:屏蔽信号集、非处理信号集

system-v 消息队列:进程发送数据块到另个进程。(避免了有名管道的同步和阻塞问题)

ftok函数:可生成唯一的键值Key

msgget函数:创建或获取消息队列对象

msgsnd函数:发送消息到消息队列

msgrcv函数:在消息队列中读取

msgctl函数:消息队列控制,设置或获取消息队列的相关属性

system-v 信号量:同步多个线程

semget函数:创建或打开一个信号量集

 semctl函数:设置或获取信号量的相关属性

semop函数:PV操作

system-v 共享内存:高效率允许多个进程共享内存

shmget函数:创建或获取共享内存对象,并返回共享内存标识符

shmat函数:映射共享内存(先映射再访问)

shmdt函数:解除共享内存映射

shmctl函数:获取或设置共享内存的相关属性


Linux系统下的ipc

早期unix系统 ipc:管道(数据传输)、信号(事件通知)、fifo(数据传输)。

System-V IPC(贝尔实验室):System-V  消息队列(数据传输、进程控制)、System-V 信号量(资源共享、进程控制)、System-V 共享内存(数据传输效率较高)

socket ipc(BSD)

posix ipc(IEEE):posix 消息队列(数据传输、进程控制)、posix 信号量(资源共享、进程控制)、posix 共享内存(数据传输)

有名管道:用于无父子关系的进程间通信。无父子关系的进程可将信息发送到某个有名管道中,并通过管道名读取消息

无名管道:用于具有父子关系的进程间通信。

信号:用于通知其他进程有何事件发送。此外,进程可以向自身发送信号,还可以获得Linux内核发出的信号。

消息队列:克服了信号的数据结构过于简单的问题,同时也解决了管道数据流无格式和缓冲区长度受限等问题。规定了每个进程的权限,避免了仿冒信息的出现。

信号量:用于解决进程的同步和相关资源抢占而设计的。

共享内存:让多个进程访问同一个内存空间,适合于数据量极大和数据结构极为复杂的进程间通信。但这种方式牺牲了系统的安全性,所有通常与其他进程间通信形式混合使用,并避免以根用户权限执行。

套接字:Linux下的程序能快速移植到其它类UNIX平台上。

无名管道(只支持具有亲缘关系的进程间通信)

无名管道实质:内核缓冲区(FIFO)。

半双工,实现全双工需要两个无名管道。两个进程建立连接后,若某一端关闭连接,而另一端依旧向管道写入数据。另一端第一次写数据后会收到RST响应,此后再写数据,内核将向进程发出SIGPIPE信号,通知该进程此连接已经断开。SIGPIPE信号的默认处理是终止程序。

是一个没有名字的特殊文件,无法使用open函数,但可以使用close函数。只能通过子进程继承文件描述符的形式来使用。读操作时管道如果为空,会阻塞进程,直到有数据写入管道。写操作时管道如果满了,会阻塞进程,直到管道读取数据。当数据被读取后,这些数据将自动被管道清除。所有文件描述符被关闭之后,无名管道被销毁。

Linux进阶-进程间通信(ipc)_第1张图片

//所需头文件
#include 

//函数原型
int pipe(int pipefd[2]);    //pipefd[0]读数据,pipefd[1]写数据

//返回值
成功:0
失败:-1
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_DATA_LEN 256

int main(int argc, char** argv)
{
        pid_t pid;
        int pipe_fd[2], status, read_length, write_length;
        char buf[MAX_DATA_LEN];
        const char data[] = "Pipe Test Program";
        memset( (void *)buf, 0, sizeof(buf));

        /* 创建管道 */
        if( pipe(pipe_fd) < 0 )
        {
                printf("pipe create error!!\r\n");
                exit(1);
        }

        if( (pid=fork()) == 0 ) /* 创建子进程并进入子进程 */
        {
                /* 关闭子进程写描述符 */
                close(pipe_fd[1]);
                /* 子进程读取管道内容 */
                if( (read_length = read(pipe_fd[0], buf, MAX_DATA_LEN)) > 0 )
                        printf("%d bytes read from the pipe is '%s'\r\n", read_length, buf);
                /* 关闭子进程读描述符 */
                close(pipe_fd[0]);
                exit(0);
        }
        else if(pid>0)          /* 父进程执行 */
        {

                /* 关闭父进程读描述符 */
                close(pipe_fd[0]);
                /* 父进程写入管道内容 */
                if( (write_length = write(pipe_fd[1], data, strlen(data))) != -1 )
                        printf("Parent write %d bytes : '%s'\r\n", write_length, data);
                /* 关闭父进程写描述符 */
                close(pipe_fd[1]);
                wait(&status);
                exit(0);
        }

        return 0;
}

有名管道(FIFO)

一般指FIFO管道,属于文件系统中的特殊文件类型,在文件系统中以文件名的形式存在,但它的数据却是存储在内存中的。我们可以在终端(命令行)上创建有名管道,也可以在程序中创建它。程序中可以查看文件stat结构体中st_mode成员的值来判断文件是否是FIFO文件。

FIFO不同于管道之外在于它提供一个路径与之关联,以FIFO的文件形式存在于系统中。它在磁盘上有对应的节点,但没有数据块(只是拥有一个名字和相应的访问权限,通过mknode系统调用或mkfifo()函数来建立)。一旦建立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程(前提是进程对FIFO有适当的访问权)。当不再被进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。

//所需头文件
#include 
#include 

//函数原型,mkfifo会根据参数filename创建特殊的FIFO文件,参数mode为该文件的模式与权限
int mkfifo(const char *filename, mode_t mode);    //文件名,权限

//返回值
成功:0
失败:-1

有文件名,可以使用open函数打开,任意进程间数据传输。读操作时管道如果为空,会阻塞进程,直到有数据写入管道。写操作时管道如果满了,会阻塞进程,直到管道读取数据。当数据被读取后,这些数据将自动被管道清除。wtite具有“原子性”。

fifo_read.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MYFIFO "/tmp/myfifo"            /* 有名管道文件名 */
#define MAX_BUFFER_SIZE PIPE_BUF        /* 4096定义在limits.h */

int main(int argc, char** argv)
{
        int fd, read_length;
        char buf[MAX_BUFFER_SIZE];

        /* 判断有名管道是否已存在,若尚未创建,则以相应的权限创建 */
        if( access(MYFIFO, F_OK) == -1 )
        {
                if( ( mkfifo(MYFIFO, 0666) < 0 ) && (errno != EEXIST) )
                {       
                        printf("Cannot create fifo error!!\r\n");
                        exit(1);
                }
        }

        /* 以只读阻塞方式打开有名管道 */
        fd = open(MYFIFO, O_WRONLY);
        if( fd == -1 )
        {
                printf("Open fifo file error!!\r\n");
                exit(1);
        }

        /* 循环读取有名管道数据 */
        while(1)
        {
                memset(buf, 0, sizeof(buf));
                if( (read_length = read(fd, buf, MAX_BUFFER_SIZE)) > 0 )
                        printf("Read '%s' from FIFO\r\n", buf);
        }

        close(fd);
        exit(0);
}

fifo_write.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MYFIFO "/tmp/myfifo"            /* 有名管道文件名 */
#define MAX_BUFFER_SIZE PIPE_BUF        /* 4096定义在limits.h */

int main(int argc, char** argv)
{
        int fd, write_length;
        char buf[MAX_BUFFER_SIZE];

        if(argc<=1)
        {
                printf("Usage: ./fifo_write string\r\n");
                exit(1);
        }

        /* 填充命令行第一个参数到buf */
        sscanf(argv[1], "%s", buf);

        /* 以只写阻塞方式打开有名管道 */
        fd = open(MYFIFO, O_RDONLY);
        if( fd == -1 )
        {
                printf("Open fifo file error!!\r\n");
                exit(1);
        }

        if( (write_length = write(fd, buf, MAX_BUFFER_SIZE)) >0 )
                printf("Write '%s' to FIFO\r\n", buf);

        /* 循环读取有名管道数据 */
        /*
        while(1)
        {
                memset(buf, 0, sizeof(buf));
                if( (read_length = read(fd, buf, MAX_BUF_SIZE)) > 0 )
                        printf("Read '%s' from FIFO\r\n", buf);
        }
        */

        close(fd);
        exit(0);
}

Linux进阶-进程间通信(ipc)_第2张图片

软中断信号(signal,信号):通知进程发生了异步事件

信号只是用来通知某进程发生了什么事件,无法给进程传递任何数据。在每个进程中,都对应一个task_struct(PCB),里面记录着进程的各种信息(包括信号的记录)。信号在task_struct中是以位图的方式由signal变量记录的。比特位的内容为是否收到信号,假如收到6号信号就会把第6个比特位置1。

信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到来。生成(raise)表示进程产生了一个信号,捕获(catch)表示进程接收到一个信号

在linux系统中,信号可能是由于系统中某些错误而产生,也可以是某个进程主动生成的一个信号

由于某些错误条件而生成的信号:如内存段冲突、浮点处理器错误或非法指令等,它们由shell和终端处理器生成并且引起中断。

由进程主动生成的信号可以作为在进程间传递通知或修改行为的一种方式,它可以明确地由一个进程发送给另一个进程,当进程捕获了这个信号就会按照程序进行相应并且去处理它。

无论何种情况,它们的编程接口都是相同的,信号可以被生成、捕获、响应或忽略。进程间可以互相发送信号,内核也可以因为内部事件而给进程发送信号,通知进程发送了某个事件。

显示信号类型:kill -l

pkill 进程名:杀死进程

硬件产生信号:执行非法指令、访问非法内存、驱动程序等

软件产生信号:控制台(Ctrl+C中断信号、Ctrl+L退出信号、Ctrl+Z停止信号)、kill命令、程序调用kill()函数

信号的处理方式:
忽略:进程当信号从来没有发生过
捕获:进程会调用相应的处理函数,进行相应的处理
默认:使用系统默认处理方式来处理信号

31号之前为普通信号,34号之后为实时信号。

服务程序运行在后台,如果想要中止它,强行杀死(Ctrl+C)不是个好办法,因为程序被杀的时候,程序突然死亡,没有释放资源,会影响系统的稳定。如果能向后台程序发送一个信号,后台程序收到这个信号后,调用一个函数,在函数中编写释放资源的代码,程序就可以有计划的退出,安全而体面。 

Linux进阶-进程间通信(ipc)_第3张图片

signal函数

//所需头文件
#include 

//函数原型
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);   

signum:信号编号
handler:信号的处理方式。
    SIG_IGN:忽略参数signum所指的信号
    SIG_DFL:恢复参数signum所指信号的处理方法为默认值
    自定义处理信号函数:信号的编号为自定义处理信号函数的参数

//返回值,实际上程序员不关心signal的返回值
成功:上一次设置的handler
失败:SIG_ERR
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void signal_handler(int sig)
{
        printf("\nthis signal number is %d \n", sig);

        if(sig == SIGINT)
        {
                printf("I have get SIGINT!\n");
                printf("The signal has been restored to the default processing mode!\n");
                /* 恢复信号为默认情况 */
                signal(SIGINT, SIG_DFL);
        }
}

int main(int argc, char** argv)
{

        printf("\nthis is an alarm test function\n");

        /* 屏蔽全部的信号 */
        for(int i=0; i<100; i++)
                signal(i, SIG_IGN);

        /* 设置信号处理的回调函数 */
        signal(SIGINT, signal_handler);

        while(1)
        {
                printf("waiting for the SIGINT signal, please enter \"Ctrl+c\"...\n");
                sleep(1);
        }

        exit(0);
}

 Linux进阶-进程间通信(ipc)_第4张图片

kill函数:向其它进程或者线程发送信号

//所需头文件
#include 
#include 

//函数原型,将参数sig指定的信号给参数pid指定的进程
int kill(pid_t pid, int sig);    //PID,信号类型
pid:
    >0:将信号传给进程号为pid的进程
    =0:将信号传给和目前进程相同进程组的所有进程,常用于父进程给子进程发送信号(注:发送信号的进程也会收到自己发出的信号)
    =-1:将信号广播传给系统内所有的进程,例如系统关机时,会像所有的登录窗口关播关机信息
sig:准备发送的信号代码,假如其值为零则没有任何信号发出,但是系统会执行错误检查,通常会利用sig值为0来检验某个进程是否仍在运行

//返回值
成功:0
失败:-1
    errno:
        EINVAL:指定的信号码无效(参数sig不合法)
        EPERM:权限不够无法传送信号给指定进程
        ESRCH:参数pid所指定的进程或进程组不存在

raise函数:向当前进程发送信号(自己给自己发信号)

//所需头文件
#include 

//函数原型
int raise(int sig);    

//返回值
成功:0
失败:1
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char** argv)
{
        pid_t pid;
        int ret;

        if( ( pid = fork() ) < 0 )
        {
                printf("fork error\n");
                exit(1);
        }else if(pid ==0)       //子进程
        {
                /* 向子进程发出SIGSTOP信号,使子进程暂停 */
                printf("Child is waiting for SIGSTOP SIGNAL!\n");

                /* 子进程停在这里 */
                raise(SIGSTOP);

                /* 子进程没有机会运行到这里 */
                printf("Child won't run here forever!\n");

                exit(0);
        }else                   //父进程
        {
                sleep(3);                                       //让子进程先执行
                if( ( ret = kill(pid, SIGKILL) == 0 ) )         //发送SIGKILL信号杀死子进程
                        printf("Parent kill %d!\n", pid);
                wait(NULL);                                     //一直阻塞直到子进程退出(杀死)
                printf("parent exit!\n");                       //父进程退出执行
        }

        exit(0);
}

Linux进阶-进程间通信(ipc)_第5张图片

信号集处理函数:屏蔽信号集、非处理信号集

屏蔽信号集:手动/自动屏蔽某些信号

非处理信号集:信号如果被屏蔽,则记录在未处理信号集。非实时信号(1~31),不排队,只留一个;实时信号(34~64),排队,保留全部。

信号集相关API 描述 参数
int sigempty(sigset_t *set) 将信号集初始化为0
int sigfillset(sigset_t *set) 将信号集初始化为1
int sigaddset(sigset_t *set,int signum) 将信号集某一位设置为1
int sigdelset(sigset_t *set,int signum) 将信号集某一位设置为0
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset) 使用设置好的信号集去修改信号屏蔽集

how:SIG_BLOCK-屏蔽某个信号(屏蔽集|set)、SIG_UNBLOCK-打开某个信号(屏蔽集&~set)、SIG~SETMASK-屏蔽集=set

oldset:保存旧的屏蔽集的值,NULL表示不保存

system-v 消息队列:进程发送数据块到另个进程。(避免了有名管道的同步和阻塞问题)

消息队列是存在Linux内核中,以链表形式来存放消息。一个消息队列由一个标识符(qid)来标识

每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。

消息队列和信号的比对

        信号承载的信息量少,而消息队列可以承载大量自定义的数据。

消息队列和管道的比对

        消息队列和有名管道一样可以和没有血缘关系的进程进行通信,同时都是通过发送和接收的方式传递数据。

        有名管道中,发送数据用write(),接收数据用read()。

        消息队列中,发送数据用msgsnd(),接收数据用msgrcv()。消息队列对每个数据都有一个最大长度的限制。

        在进程终止时,消息队列及其内容并不会被删除。

        管道只能承载无格式字节流,消息队列提供有格式的字节流,可以减少开发人员的工作量。

消息队列的特点

1.消息队列可以实现消息的随机查询,满足队列的特点但不一定要以先进先出的次序读取,可以按消息的类型读取;

2.消息队列允许一个或多个进程像它写入或者读取消息;

3.与无名管道、有名管道一样,从队列中读出消息,队列中数据会被删除;

4.消息队列是面向记录的,其中的消息具有特定的格式以及特点的优先级;

5.只能内核重启或人为,消息队列才可以删除。否则,消息队列会一直存在于内存中。

消息队列的实现包括创建和打开消息队列、发送消息、接收消息和控制消息队列。

在linux操作系统中消息队列限制值如下:

        消息队列个数最多为16个;

        消息队列总容量最多为16384字节;

        每个消息内容最多为8192字节。

System-V IPC(消息队列、信号量、共享内存)特点:独立于进程、没有文件名和文件描述符、IPC对象具有Key和ID。

消息队列用法:
定义一个唯一的键值Key(ftok)
构造消息队列(msgget)
发送特定类型消息(msgsnd)
接收特定类型消息(msgrcv)
删除消息队列(msgctl)

ftok函数:可生成唯一的键值Key

//所需头文件
#include 
#include 

//函数原型,把从pathname导出的信息和id的次序8位组合成一个整数IPC键
key_t ftok(const char *pathname, int proj_id);
pathname:指定的文件,此文件必须存在且可存取
proj_id:Project ID(低8位有效数值)

//返回值
成功:合法键值
失败:-1,错误存在errno中

msgget函数:创建或获取消息队列对象

//所需头文件
#include 
#include 
#include 

//函数原型
int msgget(key_t key, int msgflg);    //键值,模式标志
key:键值,多个进程可以通过它访问同一个消息队列。例如收、发进程都使用同一个键值即可使用同一个消息队列进行通信。特殊值IPC_PRIVATE,用于创建当前进程的私有消息队列,系统会自动产生一个未用的key来对应一个新的消息队列对象,这个消息队列一般用于进程内部的通信。
msgflg:模式标志,主要是IPC_CREAT,IPC_EXCL和权限mode(|)。
    IPC_CREAT:消息队列不存在则创建,存在则返回qid。
    IPC_CREAT|IPC_EXCL:消息队列不存在则创建,存在则报错。
    mode:IPC对象存取权限,如0666等。权限只有读和写,执行权限是无效的,例如0777和0666是等价的。


//返回值
成功:qid
失败:-1
    errno:
        EACCES:key指定的消息队列已存在,但调用进程没有权限访问它。
        EEXIST:key指定的消息队列已存在,而msgflg中同时指定IPC_CREAT|IPC_EXCL。
        ENOENT:key指定的消息队列不存在,并且msgflg中没有指定IPC_CREAT。
        ENOMEM:需要建立消息队列,但内存不足。
        ENOSPC:需要建立消息队列,但已达到系统的限制。

msgsnd函数:发送消息到消息队列

如果msgflag = IPC_NOWAIT,为非阻塞发送,当消息队列容量满或消息个数满会返回-1,并且置errno为EAGAIN错误。

如果msgflag = 0,为阻塞发送,当消息队列容量满或消息个数满会阻塞。若消息队列已被删除,则返回EIDRM错误;若被信号中断返回EINTR错误。

msgsnd()解除阻塞的条件:

        消息队列有容纳消息的空间;

        msqid代表的消息队列被删除;

        调用msgsnd()的进程被信号中断。

//所需头文件
#include 
#include 
#include 

//函数原型
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);    
msqid:消息队列ID
msgp:消息缓存区
    struct msgbuf      //发送的结构体
    {
        long mtype;    //消息标识
        char mtext[1]; //消息内容
    }
msgsz:mtext消息内容的字节数
msgflag:IPC_NOWAIT-非阻塞发送、0-阻塞发送

//返回值
成功:0
失败:-1
    errno:
        EAGAIN:参数msgflag设为IPC_NOWAIT,而消息队列已满。
        EIDRM:标识符为msqid的消息队列已被删除。
        EACCESS:无权限写入消息队列。    
        EFAULT:参数msgp指向无效的内存地址。
        EINTR:队列已满而处于等待情况下被信号中断。
        EINVAL:无效的参数msqid、msgsz或参数消息类型type小于0。

msgrcv函数:在消息队列中读取

msgrcv()解除阻塞的条件:

        消息队列中有满足条件的消息;

        msqid代表的消息队列被删除;

        调用msgsnd()的进程被信号中断。

//所需头文件
#include 
#include 
#include 

//函数原型
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);    
msqid:消息队列ID
msgp:消息缓存区
    struct msgbuf      //接收的结构体
    {
        long mtype;    //消息标识
        char mtext[1]; //消息内容
    }
msgsz:mtext消息内容的字节数
msgtyp:要接收消息的标识
    msgtyp = 0:返回队列中的第一个消息。
    msgtyp > 0:返回队列中消息类型为 msgtyp 的第一个消息(常用)。
    msgtyp < 0:返回队列中消息类型值小于或等于 msgtyp 绝对值的第一个消息。
msgflg:函数的控制属性:
    '0': 调用阻塞直到接收消息成功为止。
    'MSG_NOERROR': 若返回的消息字节数比 nbytes 字节数多,则消息就会截短到 nbytes 字节,且不通知消息发送进程。
    IPC_NOWAIT: 调用进程会立即返回。若没有收到消息则立即返回 -1。
msgflag:IPC_NOWAIT-非阻塞读取、MSG_NOERROR-截断消息、0-阻塞读取

//返回值
成功:0
失败:-1
    errno:
        E2BIG:消息数据长度大于msgsz,而msgflag没有设置IPC_NOERROR。
        EIDRM:标识符为msqid的消息队列已被删除。
        EACCESS:无权限读取该消息队列。
        EFAULT:参数msgp指向无效的内存地址。
        ENOMSG:参数msgflg设为IPC_NOWAIT,而消息队列中无消息可读。
        EINTR:等待读取队列内的消息情况下被信号中断。

msgctl函数:消息队列控制,设置或获取消息队列的相关属性

//所需头文件
#include 
#include 
#include 

//函数原型
ssize_t msgctl(int msqid, int cmd, struct msqid_ds *buf);    
msqid:消息队列ID
cmd:
    IPC_STAT:获取消息队列的属性信息。存放在msqid_ds类型的buf中。
    IPC_SET:设置消息队列的属性。需要先设置msg_perm.uid、msg_perm.gid、msg_perm.mode、msg_qbytes,存放在msqid_ds中。
    IPC_RMID:删除消息队列。
buf:相关结构体缓冲区

//返回值
成功:0
失败:-1
    errno:
        EACCESS:参数cmd为IP_STAT,但无权限读取该消息队列。
        EFAULT:参数buf指向无效的内存地址。
        EIDRM:标识符为msqid的消息队列已被删除。    
        EINVAL:无效的参数cmd或msqid。
        EPERM:参数cmd为IPC_SET或IPC_RMID,但没有足够的权限执行。
msgsnd.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFFER_SIZE 512

typedef struct
{
        long msg_type;
        char msg_text[BUFFER_SIZE];
}message;

int main(int argc, char** argv)
{
        int qid;
        key_t key;
        message msg;

        /* 根据不同的路径和关键字产生标准的key */
        if( ( key = ftok("/tmp", 100) ) == -1 )
        {
                printf("ftok error\n");
                exit(1);
        }else
        {
                printf("key:%d\n", key);
        }

        /* 创建消息队列 */
        if( ( qid = msgget(key, IPC_CREAT|0666) ) == -1 )
        {
                printf("msgget eeror\n");
                exit(1);
        }else
        {
                printf("qid:%d\n", qid);
        }

        while(1)
        {
                printf("Enter some message to the queue:");
                /* 从指定的流 stream 读取一行,并把它存储在str所指向的字符串内。
                   当读取(n-1)个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止 */
                if( ( fgets(msg.msg_text, BUFFER_SIZE, stdin) ) == NULL )
                {
                        puts("gets error!\n");
                        exit(1);
                }
                msg.msg_type = getpid();

                /* 添加消息到消息队列 */
                if( ( msgsnd(qid, &msg, strlen(msg.msg_text), 0) ) < 0 )
                {
                        puts("message send error!\n");
                        exit(1);
                }

                if( strncmp(msg.msg_text, "quit", 4) == 0 )
                        break;
        }
        
        exit(0);
}


msgrcv.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFFER_SIZE 512

typedef struct
{
        long msg_type;
        char msg_text[BUFFER_SIZE];
}message;

int main(int argc, char** argv)
{
        int qid;
        key_t key;
        message msg;

        /* 根据不同的路径和关键字产生标准的key */
        if( ( key = ftok("/tmp", 100) ) == -1 )
        {
                printf("ftok error\n");
                exit(1);
        }else
        {
                printf("key:%d\n", key);
        }

        /* 创建消息队列 */
        if( ( qid = msgget(key, IPC_CREAT|0666) ) == -1 )
        {
                printf("msgget eeror\n");
                exit(1);
        }else
        {
                printf("qid:%d\n", qid);
        }

        do
        {
                /* 读取消息队列 */
                memset(msg.msg_text, 0, BUFFER_SIZE);
                if( ( msgrcv(qid, (void *)&msg, BUFFER_SIZE, 0, 0) ) < 0 )
                {
                        puts("msgrcv error!\n");
                        exit(1);
                }
                printf("The message from process %ld : %s", msg.msg_type, msg.msg_text);
        }while(strncmp(msg.msg_text, "quit", 4));

        /* 从系统内核中移走消息队列 */
        if( ( msgctl(qid, IPC_RMID, NULL)) < 0 )
        {
                printf("msgctl error!\n");
                exit(1);
        }

        exit(0);
}

 Linux进阶-进程间通信(ipc)_第6张图片

system-v 信号量:同步多个线程

消息队列的作用是进程间传递消息,而信号量的作用是为了同步多个线程

信号量本质:计数器。

信号量的值:空闲资源的个数

信号量操作:PV操作(原子性,单指令的操作,单指令的执行是不会被打断的)。P-申请资源,V-释放资源。

信号量常见用途:

        同步对一块共享内存的访问,以防止出现一个进程在访问共享内存的同时,另一个进程在更新这块共享内存。

        fork()之后同步父进程和子进程。

信号量用法:
定义一个唯一的键值Key(ftok)
构造信号量(semget)
初始化信号量(semctl SETVAL)
定义信号量进行P/V操作(semop)
删除信号量(semctl RMID)

semget函数:创建或打开一个信号量集

//所需头文件
#include 
#include 
#include 

//函数原型
int semget(key_t key, int nsems, int msgflg);    //键值,数量,IPC_CREAT(信号量不存在则创建)|权限
    key:键值
    nsems:信号量个数
    semflsg:
        IPC_CREAT:不存在则创建。
        IPC_CREAT | IPC_EXCL:不存在则创建,存在则报错。


//返回值
成功:信号量ID
失败:-1

 semctl函数:设置或获取信号量的相关属性

//所需头文件
#include 
#include 
#include 

//函数原型
int semctl(int semid, int semnum, int cmd, union semun arg);    
semid:信号量ID
semnum:信号量编号。表示信号量集中的第semnum个信号量。它的取值范围: 0 ~ nsems-1 。
cmd:
    IPC_STAT:获取信号量的属性信息,存放在semid_ds的buf里。
    IPC_SET:设置信号量的属性。
    IPC_RMID:删除信号量。
    SETVAL:设置第semnum个信号量的值。
arg(可选项):
    union semun
    {
        int val;                //供IPC_SETVAL使用
        struct semid_ds *buf;   //供IPC_STAT|IPC_SET使用
    }

//返回值
成功:由cmd类型决定
失败:-1

semop函数:PV操作

//所需头文件
#include 
#include 
#include 

//函数原型
int semop(int semid, struct sembuf *sops, size_t nsops);    
semid:信号量ID
sops:
    struct sembuf
    {
        short sem_num;    //信号量编号,它的取值范围: 0 ~ nsems-1
        short sem_op;     //信号量P/V操作。-1为p操作,1为v操作
        short sem_flg;    //信号量行为,SEM_UNDO-进程若最后没释放信号量,则释放
    }
nsops:信号量数量

//返回值
成功:0
失败:-1

sem.h

#ifndef __SEM_H
#define __SEM_H

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

int init_sem(int sem_id, int init_value);
int del_sem(int sem_id);
int sem_p(int sem_id);
int sem_v(int sem_id);

#endif

sem.c

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

union semun
{
        int val;                //供semctl()函数cmd = SETVAL时使用
        struct semid_ds *buf;   //供semctl()函数cmd = IPC_STAT|IPC_SET时使用
};

/* 初始化信号量 */
int init_sem(int sem_id, int init_value)
{
        union semun sem_union;

        sem_union.val = init_value;

        /* 信号量ID,信号量编号,命令,联合体 */
        if( semctl( sem_id, 0, SETVAL, sem_union) == -1 )
        {
                printf("Initialize semaphore error!\n");
                return -1;
        }else
        {
                printf("Initialize semaphore!\n");
        }
}

/* 删除信号量 */
int del_sem(int sem_id)
{
        union semun sem_union;

        /* 信号量ID,信号量编号,命令,联合体 */
        if( semctl( sem_id, 0, IPC_RMID, sem_union) == -1 )
        {
                printf("Delete semaphore error!\n");
                return -1;
        }else
        {
                printf("Delete semaphore!\n");
        }
}

/* p操作 */
int sem_p(int sem_id)
{
        struct sembuf sops;

        sops.sem_num = 0;               //信号量的编号:0~nsems-1
        sops.sem_op  = -1;              //表示p操作
        sops.sem_flg = SEM_UNDO;        //系统自动释放在系统中残留的信号量

        /* 信号量ID,结构体,信号量数量 */
        if( semop( sem_id, &sops, 1) == -1 )
        {
                perror("p operation error!\n");
                return -1;
        }else
        {
                printf("p operation successful!\n");
                return 0;
        }
}

/* v操作 */
int sem_v(int sem_id)
{
        struct sembuf sops;

        sops.sem_num = 0;               //信号量的编号:0~nsems-1
        sops.sem_op  = 1;               //表示v操作
        sops.sem_flg = SEM_UNDO;        //系统自动释放在系统中残留的信号量

        /* 信号量ID,结构体,信号量数量 */
        if( semop( sem_id, &sops, 1) == -1 )
        {
                perror("v operation error!\n");
                return -1;
        }else
        {
                printf("v operation successful!\n");
                return 0;
         }
}

main.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "sem.h"

#define DELAY_TIME 3

int main(int argc, char** argv)
{
        int sem_id;
        pid_t pid;

        /* 创建信号量:键值,数量,模式 */
        sem_id = semget((key_t)6666, 1, IPC_CREAT|0666);

        /* 初始化信号量 */
        init_sem(sem_id, 0);

        if( ( pid = fork() ) == -1)
        {
                perror("rork error!\n");
        }else if(pid == 0)      //子进程
        {
                printf("Child process will wait for some seconds...\n");
                sleep(DELAY_TIME);
                printf("the child process is running...\n");
                sem_v(sem_id);
        }else                   //父进程
        {
                sem_p(sem_id);
                printf("the father process is running...\n");
                sem_v(sem_id);
                del_sem(sem_id);
        }

        exit(0);
}

Linux进阶-进程间通信(ipc)_第7张图片

system-v 共享内存:高效率允许多个进程共享内存

共享内存可以在多个进程间共享和传递数据,进程间需要共享的数据被放在共享内存区域,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间。

例如,进程1的虚拟地址和进程2的虚拟地址实际上都映射在同一块物理地址,就像在线文档一样。

ipc中共享内存效率最高,因为不再涉及内存。

共享内存属于临界资源,在某一时刻最多只能有一个进程对其操作(读/写数据),一般需要配合信号量、互斥锁等协调机制。

信号量用法:
定义唯一的键值Key(ftok)
构造共享内存对象(shmget)
共享内存映射(shmat)
解除共享内存映射(shmdt)
删除共享内存映射(shmctl RMID) 

shmget函数:创建或获取共享内存对象,并返回共享内存标识符

//所需头文件
#include 
#include 

//函数原型
int shmget(key_t key, size_t size, int shmflg);
    key:共享内存键值
    size:共享内存大小,一般设置为页(4KB)的整数倍。
    shmflg:模式|权限
        IPC_CREAT:共享内存不存在则创建,存在则返回共享内存标识符。
        IPC_EXCL:共享内存不存在则创建,存在则报错。
        

//返回值
成功:共享内存ID
失败:-1
    EACCES:指定的消息队列已存在,但调用进程没有权限访问它
    EEXIST:key指定的消息队列已存在,而msgflg中同时指定IPC_CREAT和IPC_EXCL标志
    EINVAL:创建共享内存时参数size小于SHMMIN或大于SHMMAX。
    ENFILE:已达到系统范围内打开文件总数的限制。
    ENOENT:给定的key不存在任何共享内存,并且未指定IPC_CREAT。
    ENOMEM:内存不足,无法为共享内存分配内存。
    EACCES:没有权限。

shmat函数:映射共享内存(先映射再访问)

//所需头文件
#include 
#include 

//共享内存映射只能以“只读”或“读写”方式映射,不能“只写”映射
//函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
    shmid:共享内存ID
    shmaddr:映射地址,NULL为自动分配
    shmflg:SHM_RDONLY-只读方式映射,0-可读可写

//返回值
成功:共享内存首地址
失败:-1

shmdt函数:解除共享内存映射

//所需头文件
#include 
#include 

//该函数并不删除所指定的共享内存区, 而只是将先前用shmat()函数映射好的共享内存脱离当前进程,共享内存还是存在于物理内存中。
//函数原型
int shmdt(const void *shmaddr);
shmaddr:共享内存映射首地址

//返回值
成功:0
失败:-1,将错误原因存于error中。

shmctl函数:获取或设置共享内存的相关属性

//所需头文件
#include 
#include 

//函数原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    shmid:共享内存ID
    cmd:
        IPC_STAT:获取共享内存的属性信息。
        IPC_SET:设置共享内存的属性。
        IPC_RMID:删除共享内存。
    buf:属性缓冲区
        struct shmid_ds {
            struct ipc_perm shm_perm;    /* 所有权和权限 */
            size_t          shm_segsz;   /* 共享内存尺寸(字节) */
            time_t          shm_atime;   /* 最后一次映射时间 */
            time_t          shm_dtime;   /* 最后一个解除映射时间 */
            time_t          shm_ctime;   /* 最后一次状态修改时间 */
            pid_t           shm_cpid;    /* 创建者PID */
            pid_t           shm_lpid;    /* 后一次映射或解除映射者PID */
            shmatt_t        shm_nattch;  /* 映射该SHM的进程个数 */
            ...
        };
        struct ipc_perm {
            key_t          __key;    /* 该共享内存的键值key */
            uid_t          uid;      /* 所有者的有效UID */
            gid_t          gid;      /* 所有者的有效GID */
            uid_t          cuid;     /* 创建者的有效UID */
            gid_t          cgid;     /* 创建者的有效GID */
            unsigned short mode;     /* 读写权限 + SHM_DEST + SHM_LOCKED 标记 */
            unsigned short __seq;    /* 序列号 */
        };

//返回值
成功:由cmd类型决定
失败:-1

sem.h

#ifndef __SEM_H
#define __SEM_H

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

int init_sem(int sem_id, int init_value);
int del_sem(int sem_id);
int sem_p(int sem_id);
int sem_v(int sem_id);

#endif

sem.c

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

union semun
{
        int val;                //供semctl()函数cmd = SETVAL时使用
        struct semid_ds *buf;   //供semctl()汉斯cmd = IPC_STAT|IPC_SET时使用
};

/* 初始化信号量 */
int init_sem(int sem_id, int init_value)
{
        union semun sem_union;

        sem_union.val = init_value;

        /* 信号量ID,信号量编号,命令,联合体 */
        if( semctl( sem_id, 0, SETVAL, sem_union) == -1 )
        {
                printf("Initialize semaphore error!\n");
                return -1;
        }else
        {
                printf("Initialize semaphore!\n");
        }
}

/* 删除信号量 */
int del_sem(int sem_id)
{
        union semun sem_union;

        /* 信号量ID,信号量编号,命令,联合体 */
        if( semctl( sem_id, 0, IPC_RMID, sem_union) == -1 )
        {
                printf("Delete semaphore error!\n");
                return -1;
        }else
        {
                printf("Delete semaphore!\n");
        }
}

/* p操作 */
int sem_p(int sem_id)
{
        struct sembuf sops;

        sops.sem_num = 0;               //信号量的编号:0~nsems-1
        sops.sem_op  = -1;              //表示p操作
        sops.sem_flg = SEM_UNDO;        //系统自动释放在系统中残留的信号量

        /* 信号量ID,结构体,信号量数量 */
        if( semop( sem_id, &sops, 1) == -1 )
        {
                perror("p operation error!\n");
                return -1;
        }else
        {
                printf("p operation successful!\n");
                return 0;
        }
}

/* v操作 */
int sem_v(int sem_id)
{
        struct sembuf sops;

        sops.sem_num = 0;               //信号量的编号:0~nsems-1
        sops.sem_op  = 1;               //表示v操作
        sops.sem_flg = SEM_UNDO;        //系统自动释放在系统中残留的信号量

        /* 信号量ID,结构体,信号量数量 */
        if( semop( sem_id, &sops, 1) == -1 )
        {
                perror("v operation error!\n");
                return -1;
        }else
        {
                printf("v operation successful!\n");
                return 0;
         }
}

main.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "sem.h"

#define DELAY_TIME 3

int main(int argc, char** argv)
{
        int sem_id, shm_id;
        char *addr;
        pid_t pid;

        /* 创建信号量:键值,数量,模式 */
        sem_id = semget((key_t)6666, 1, IPC_CREAT|0666);
        /* 创建共享内存:键值,共享内存大小,模式 */
        shm_id = shmget((key_t)7777, 1024, IPC_CREAT|0666);

        /* 初始化信号量 */
        init_sem(sem_id, 0);

        if( ( pid = fork() ) == -1)
        {
                perror("rork error!\n");
        }else if(pid == 0)      //子进程
        {
                printf("Child process will wait for some seconds...\n");
                sleep(DELAY_TIME);

                /* 映射共享内存,映射地址系统自动分配 */
                if( ( addr = shmat(shm_id, NULL, 0) ) == (void *)-1 )
                {
                        printf("child shmat error!\n");
                        exit(-1);
                }

                memcpy(addr, "hello couvrir", 14);
                printf("the child process is running...\n");
                sem_v(sem_id);
        }else                   //父进程
        {
                sem_p(sem_id);
                printf("the father process is running...\n");

                /* 映射共享内存,映射地址系统自动分配 */
                if( ( addr = shmat(shm_id, NULL, 0) ) == (void *)-1 )
                {
                        printf("father shmat error!\n");
                        exit(-1);
                }

                printf("share memory string:%s\n", addr);

                /*解除共享内存映射*/
                shmdt(addr);

                sem_v(sem_id);
                del_sem(sem_id);
        }

        exit(0);
}

Linux进阶-进程间通信(ipc)_第8张图片

你可能感兴趣的:(#,linux基础之路,linux)