【Linux系统编程】进程间通信

        进程间通信简称IPC (Inter process communication),进程间通信就是在不同进程之间传播或交换信息。

​        由于各个运行进程之间具有独立性,这个独立性主要体现在数据层面,而代码逻辑层面可以私有也可以公有(例如父子进程),因此各个进程之间要实现通信是非常困难的。

​        各个进程之间若想实现通信,一定要借助第三方资源,这些进程就可以通过向这个第三方资源写入或是读取数据,进而实现进程之间的通信,这个第三方资源实际上就是操作系统提供的一段内存区域。

        因此,进程间通信的本质就是,让不同的进程看到同一份资源(内存,文件、内核缓冲等)。由于这份资源可以由操作系统中的不同模块提供,因此出现了不同的进程间通信方式。

进程间通信的目的:

​    ●数据传输:一个进程需要将它的数据发送给另一个进程。

​    ●资源共享:多个进程之间共享同样的资源。

​    ●通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件,比如进程终止时需要通知其父进程。(信号)

​    ●*进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。        

管道

特点:单向通信、自己能阻塞。分不开两条消息。

​        管道是Unix中最古老的进程间通信的形式,我、们把从一个进程连接到另一个进程的数据流称为一个“管道”。

例如,统计我们登录用户个数。

​        其中,who命令和wc命令都是两个程序,当它们运行起来后就变成了两个进程,who进程通过标准输出将数据打到"管道"当中,wc进程再通过标准输入从"管道"当中读取数据,至此便完成了数据的传输,进而完成数据的进一步加工处理。

匿名管道

​    匿名管道用于进程间通信,且仅限于本地关联进程之间的通信

​    进程间通信的本质就是,让不同的进程看到同一份资源,使用匿名管道实现父子进程间通信的原理就是,让两个父子进程先看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或是读取操作,进而实现父子进程间通信。

注意:

        这里父子进程看到的同一份文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝。

        管道虽然用的是文件的方案,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有IO参与会降低效率,而且也没有必要。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在。

1、它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端

2、只能用于具有亲缘关系的进程间通信(父子进程、兄弟进程等)。

3、可以看成是一种特殊的文件,对它的读写也可以使用普通的read、write等函数,但它不是普通的文件,不属于任何文件系统,并且只能存在于内存中。

pipe函数

函数描述:

        创建匿名管道

函数原型:

int pipe(int pipefd[2]);

函数参数:

        pipefd是一个传出参数,用于返回两个指向管道读端和写端的文件描述符:

                ●pipefd[0]:管道读端的文件描述符

                ●pipefd[1]:管道写端的文件描述符

函数返回值:

        ●成功返回0

        ●失败返回-1,设置errno

        在创建匿名管道实现父子进程间通信的过程中,需要pipe 函数和fork 函数搭配使用,具体步骤如下:

1、父进程调用pipe函数创建管道。

2、父进程创建子进程。

3、父进程关闭写端,子进程关闭读端。

注意:

        管道只能够进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端。

        从管道写端写入的数据会被存到内核缓冲,直到从管道的读端被读取。

#include<stdio.h>  
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/wait.h>

int main(int argc, char* argv[])
{
    //创建管道,用于两个进程间通信
    int pipefd[2];
    pipe(pipefd);

    //创建子进程
    int pid = fork();
    if(pid == 0){//子进程,向管道写数据,关闭读端
        close(pipefd[0]);
        sleep(5);
        int write_count = write(pipefd[1], "hello", 5);
        printf("write_count = %d\n", write_count);
    }
    else{//父进程,向管道读数据,关闭写端
        close(pipefd[1]);
        char buf[64];
        int read_count = read(pipefd[0], buf, sizeof(buf));
        printf("read_count = %d\n", read_count);
        if(read_count > 0){
            buf[read_count] = '\0';
            printf("buf = %s\n", buf);
        }
    }
    return 0;
} 

匿名管道的特性:

●管道中没有数据: write返回成功写入的字节数,读端进程阻塞在read 上

●管道中有数据没满: write返回成功写入的字节数,read返回读到的字节数

●管道已满:写端进程阻塞在write 上, read返回读到的字节数

●写端全部关闭: read正常读,返回读到的字节数(没有数据返回0,不阻塞)

●读端全部关闭:写端进程write会异常终止进程(被SIGPIPE信号杀死的)

读管道:
        ●管道中有数据,read返回实际读到的字节数。
        ●管道中无数据:
            写端被全部关闭,read返回0(相当于读到文件的末尾)
            写端没有完全关闭,read阻塞等待

写管道:
    ●管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
    ●管道读端没有全部关闭:
        管道已满,write阻塞
        管道没有满,write将数据写入,并返回实际写入的字节数

前两种情况是上一个代码例子,接下来是后三种情况:

#include<stdio.h>  
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/wait.h>
//管道已满:写端进程阻塞在write 上, read返回读到的字节数
int main(int argc, char* argv[])
{
    //创建管道,用于两个进程间通信
    int pipefd[2];
    pipe(pipefd);

    //创建子进程
    int pid = fork();
    if(pid == 0){//子进程,向管道写数据,关闭读端
        close(pipefd[0]);
        sleep(5);
        while(1){//模拟管道已满
            int write_count = write(pipefd[1], "hello", 5);
            printf("write_count = %d\n", write_count);
        }
    }
    else{//父进程,向管道读数据,关闭写端
        close(pipefd[1]);
        char buf[64];
        int read_count = read(pipefd[0], buf, sizeof(buf));
        printf("read_count = %d\n", read_count);
        if(read_count > 0){
            buf[read_count] = '\0';
            printf("buf = %s\n", buf);
        }
        while(1);
    }
    return 0;
} 
#include<stdio.h>  
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/wait.h>
//管道已满,写端关闭, read正常读
int main(int argc, char* argv[])
{
    //创建管道,用于两个进程间通信
    int pipefd[2];
    pipe(pipefd);

    //创建子进程
    int pid = fork();
    if(pid == 0){//子进程,向管道写数据,关闭读端
        close(pipefd[0]);
        
        while(1){//模拟管道已满
            int write_count = write(pipefd[1], "hello", 5);
            printf("write_count = %d\n", write_count);
        }
    }
    else{//父进程,向管道读数据,关闭写端
        close(pipefd[1]);
        sleep(15);
        char buf[64];
        int read_count = read(pipefd[0], buf, sizeof(buf));
        printf("read_count = %d\n", read_count);
        if(read_count > 0){
            buf[read_count] = '\0';
            printf("buf = %s\n", buf);
        }
        while(1);
    }
    return 0;
} 

在管道中读取出一部分之后为什么不再继续往里写了呢,因为读出去留下的空间不够再写入一个文件的大小。

#include<stdio.h>  
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/wait.h>

int main(int argc, char* argv[])
{
    //创建管道,用于两个进程间通信
    int pipefd[2];
    pipe(pipefd);

    //创建子进程
    int pid = fork();
    if(pid == 0){//子进程,向管道写数据,关闭读端
        close(pipefd[0]);
        
        while(1){//模拟管道已满
            int write_count = write(pipefd[1], "hello", 5);
            printf("write_count = %d\n", write_count);
        }
    }
    else{//父进程,向管道读数据,关闭写端
        close(pipefd[1]);
        sleep(1);
        char buf[1024];
        int read_count;
        while(1){
            read_count = read(pipefd[0], buf, sizeof(buf));
            printf("read_count = %d\n", read_count);
            sleep(1);
        }
    }
    return 0;
} 

写完了读,读三秒又继续写

#include<stdio.h>  
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/wait.h>
//写端全部关闭: read正常读,返回读到的字节数(没有数据返回0,不阻塞)
int main(int argc, char* argv[])
{
    //创建管道,用于两个进程间通信
    int pipefd[2];
    pipe(pipefd);

    //创建子进程
    int pid = fork();
    if(pid == 0){//子进程,向管道写数据,关闭读端
        close(pipefd[0]);

        int write_count = write(pipefd[1], "hello", 5);
        printf("write_count = %d\n", write_count);
        return 0;
    }
    else{//父进程,向管道读数据,关闭写端
        close(pipefd[1]);
        sleep(1);
        char buf[1];
        int read_count;
        while(1){
            read_count = read(pipefd[0], buf, sizeof(buf));
            printf("read_count = %d\n", read_count);
            sleep(1);
        }
    }
    return 0;
} 

读完五个1之后后边都是0,不会阻塞

#include<stdio.h>  
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/wait.h>

int main(int argc, char* argv[])
{
    //创建管道,用于两个进程间通信
    int pipefd[2];
    pipe(pipefd);

    //创建子进程
    int pid = fork();
    if(pid == 0){//子进程,向管道写数据,关闭读端
        close(pipefd[0]);
        sleep(2);
        while(1){
            int write_count = write(pipefd[1], "hello", 5);
            printf("write_count = %d\n", write_count);
            sleep(1);
        }
    }
    else{//父进程,向管道读数据,关闭写端
        close(pipefd[1]);
        char buf[1];
        int read_count;
        read_count = read(pipefd[0], buf, sizeof(buf));
        printf("read_count = %d\n", read_count);
        return 0;
    }
    return 0;
} 

读端关闭之后,写端也不会继续写了。

练习:实现ls / | wc -l

​    借助管道和execlp 函数,实现ls / | wc -l

#include<stdio.h>  
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/wait.h>

int main(int argc, char* argv[])
{
    int pipefd[2];
    pipe(pipefd);

    int pid = fork();
    if(pid == 0){//子进程调用ls,将管道写文件描述符复制给1
        close(pipefd[0]);//关闭读管道
        dup2(pipefd[1], 1);//输出到管道
        execlp("ls", "ls", "/", NULL);
    }
    else{//父进程调用wc,将管道读文件描述符复制给0
        close(pipefd[1]);//关闭写管道
        dup2(pipefd[0], 0);
        execlp("wc", "wc", "-l", NULL);
    }
    return 0;
}

命名管道

​    匿名管道只能用于具有共同祖先的进程(具有亲缘关系的进程)之间的通信,通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道。如果要实现两个毫不相关进程之间的通信,可以使用命名管道来做到。命名管道就是一种特殊类型的文件,两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了。

​    命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。

创建命名管道文件:使用mkfifo命令

可以看到,创建出来的文件的类型是p,代表该文件是命名管道文件。

那个0是磁盘大小,写东西进去也是0

使用这个命名管道文件就像使用普通文件一样,就能实现两个进程之间的通信了。

mkfifo函数

函数描述:

        程序中创建命名管道

头文件:

#include<sys/types.h>
#include<sys/stat.h>

函数原型:

int mkfifo(const char *pathname, mode_t mode);

函数参数:

​        ●pathname:表示要创建的命名管道文件

​        ●mode:表示创建命名管道文件的默认权限

函数返回值:

​        ●成功,返回0

​        ●失败,返回-1

命名管道在父子进程间通信:

#include<stdio.h>  
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>

int main(int argc, char* argv[])
{
    mkfifo("./fifo", 0644);
    int fd = open("./fifo", O_RDWR);
    int pid = fork();
    if(pid == 0){
        sleep(1);
        write(fd, "hello world", 11);
    }
    else {
        char buf[64];
        int read_count = read(fd, buf, sizeof(buf));
        buf[read_count] = '\0';
        printf("read_count = %d, buf = %s\n", read_count, buf);
    }
    return 0;
}

#include<stdio.h>  
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>

int main(int argc, char* argv[])
{
    mkfifo("./fifo", 0644);
    int fd = open("./fifo", O_RDWR);
    int pid = fork();
    if(pid == 0){
        sleep(1);
        write(fd, "hello world", 11);
    }
    else {
        sleep(30);
        char buf[64];
        int read_count = read(fd, buf, sizeof(buf));
        buf[read_count] = '\0';
        printf("read_count = %d, buf = %s\n", read_count, buf);
    }
    return 0;
}

那个0是磁盘大小,写东西进去也是0

命名管道在没有血缘关系的进程间通信:

wfifo.c

int main(int argc, char* argv[])
{
    int fd = open("./fifo", O_WRONLY);
    printf("fd = %d\n", fd);
    write(fd, "hello, fifo", 11);
    return 0;
}

rfifo.c

int main(int argc, char* argv[])
{
    int fd = open("./fifo", O_RDONLY);
    char buf[64];
    int ret = read(fd, buf, sizeof(buf));
    buf[ret] = '\0';
    printf("read count = %d, buf = $s\n", ret, buf);
    return 0;
}

读进程先出现,写进程才能有结果。

命名管道的打开规则:

​    ●读进程打开FIFO,并且没有写进程打开时:

​        没有O_NONBLOCK:阻塞直到有写进程打开该FIFO

​        有O_NONBLOCK:立刻返回成功0

​    ●写进程打开FIFO,并且没有读进程打开时:

​        没有O_NONBLOCK:阻塞直到有读进程打开该FIFO

​        有O_NONBLOCK:立刻返回失败-1,错误码为ENXIO

再给wfifo.c加一个O_NONBLOCK  非阻塞

int main(int argc, char* argv[])
{
    int fd = open("./fifo", O_WRONLY | O_NONBLOCK);
    printf("fd = %d\n", fd);
    write(fd, "hello, fifo", 11);
    return 0;
}

在不执行./rfifo的时候,执行./wfifo会返回-1.

如果给rfifo.c加一个O_NONBLOCK  非阻塞

int main(int argc, char* argv[])
{
    int fd = open("./fifo", O_RDONLY | O_NONBLOCK);
    char buf[64];
    int ret = read(fd, buf, sizeof(buf));
    buf[ret] = '\0';
    printf("read count = %d, buf = $s\n", ret, buf);
    return 0;
}

练习

使用FIFO完成两个程序互发消息聊天

a.c

int main(int argc, char* argv[])
{
    
    char buf[1024];
    int read_count;

    int pid = fork();
    if(pid == 0) {
        int fd = open("./a_write_fifo", O_WRONLY);
        while(1) {//发送
            read_count = read(0, buf, sizeof(buf));
            write(fd, buf, read_count);
        }
    } else {
        int fd = open("./a_read_fifo", O_RDONLY);
        while(1) {//接收
            read_count = read(fd, buf, sizeof(buf));
            write(1, buf, read_count);
        }
    }
    return 0;
}

b.c

int main(int argc, char* argv[])
{
    char buf[1024];
    int read_count;

    int pid = fork();
    if(pid == 0) {
        int fd = open("./a_read_fifo", O_WRONLY);
        while(1) {//发送
            read_count = read(0, buf, sizeof(buf));
            write(fd, buf, read_count);
        }
    } else {
        int fd = open("./a_write_fifo", O_RDONLY);
        while(1) {//接收
            read_count = read(fd, buf, sizeof(buf));
            write(1, buf, read_count);
        }
    }
}

内存映射

特点:没办法进行同步操作。比管道效率更高。会覆盖前一个消息

​        内存映射(Memory-mapped lO)是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。

映射分为两种:

​    ●文件映射:将文件的一部分映射到调用进程的虚拟内存中。对文件映射部分的访问转化为对相应内存区域的字节操作。映射页面会按需自动从文件中加载。

​    ●匿名映射:一个匿名映射没有对应的文件。其映射页面的内容会被初始化为0。一个进程所映射的内存可以与其他进程的映射共享,共享的两种方式:

​            ●两个进程对同一文件的同一区域映射。

​            ●fork()创建的子进程继承其父进程的映射。

mmap()函数

函数描述:

        在调用进程的虚拟地址空间中创建一个新内存映射。

头文件:

<sys/mman.h>

函数原型:

void *mmap(void *addr,size_t length, int prot, int flags, int fd, off_t offset);

函数参数:

●addr:指向欲映射的内存起始地址,通常设为NULL,代表系统自动选定地址。

●length:映射的长度。

●prot:映射区域的保护方式:

        PROT_READ:映射区域可读取

        PROT_WRITE:映射区域可修改

●flags:影响映射区域的特性。必须指定MAP_SHARED或 MAP_PRIVATE

        MAP_SHARED:创建共享映射。对映射的写入会写入文件里,其他共享映射的进程可见

        MAP_PRIVATE:创建私有映射。对映射的写入不会写入文件里,其他映射进程不可见

        MAP_ANONYMOUS:创建匿名映射。此时会忽略参数fd(设为-1),不涉及文件,没有血缘关系的进程不能共享

●fd:要映射的文件描述符,匿名映射设为-1

●offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小(4k)的整数倍。

函数返回值:

        若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED( - 1),错误原因存于errno 中。

munmap()函数

函数描述:

        解除映射区域

头文件:

<sys/mman.h>

函数原型:

int munmap(void *addr,size_t length);   

函数参数:

        ●addr:指向要解除映射的内存起始地址

        ●length:解除映射的长度

父子进程间通信:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<sys/mman.h>

int main(int argc, char* argv[])
{
    //操作内存
    char* ptr = (char*)mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    int pid = fork();
    if(pid == 0){
        strcpy(ptr, "hello world");
        printf("child ptr = %s\n", ptr);
    } else{
        sleep(1);
        printf("parent ptr = %s\n", ptr);
    }
    return 0;
}C

没有血缘关系进程间通信:

wmmap.c

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<stdlib.h>
#include<sys/mman.h>

int main(int argc, char* argv[])
{
    //创建内存映射
    int fd = open("./mmap.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    ftruncate(fd, 1024);//扩展文件大小,空文件内存映射会报错
    char* ptr = (char*)mmap(NULL, 1024, PROT_WRITE, MAP_SHARED, fd, 0);
        
    if(ptr == MAP_FAILED) {
        perror("mmap error");
        exit(1);
    }
    int i = 0;
    while(1) {
        sprintf(ptr, "--------%d---------\n", i++);
        sleep(1);
    }
    return 0;
}

rmmap.c

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<stdlib.h>
#include<sys/mman.h>

int main(int argc, char* argv[])
{
    int fd = open("./mmap.txt", O_RDONLY);

    char* ptr = (char*)mmap(NULL, 1024, PROT_READ, MAP_SHARED, fd, 0);

    if(ptr == MAP_FAILED) {
        perror("mmap error");
        exit(1);
    }
    
    int i = 0;
    while(1) {
        printf("ptr = %s\n", ptr);
        sleep(1);
    }
    return 0;
}

消息队列

​        消息队列是保存在内核中的消息链表,消息队列是面向消息进行通信的,一次读取一条完整的消息,每条消息中还包含一个整数表示优先级,可以根据优先级读取消息。进程A可以往队列中写入消息,进程B读取消息。并且,进程A写入消息后就可以终止,进程B在需要的时候再去读取。

每条消息通常具有以下属性:

​    ●一个表示优先级的整数

​    ●消息数据部分的长度

​    ●消息数据本身

消息队列函数

头文件:

#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>

打开和关闭消息队列:

mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);
int mq_close(mqd_t mqdes);

获取和设置消息队列属性:

int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr,struct mq_attr *oldattr);

在队列中写入和读取一条消息:

int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned intmsg_prio);
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int*msg_prio);

删除消息队列名:

int mq_unlink(const char *name);

函数参数和返回值:

●name:消息队列名

●flag:打开方式,类似open函数。

        o必选项:O_RDONLY,O_WRONLY,O_RDWR

        o可选项:O_NONBLOCK,O_CREAT,O_EXCL

● mode:访问权限,oflag 中含有O_CREAT且消息队列不存在时提供该参数

●attr:队列属性,open 时传NULL表示默认属性

●mqdes:表示消息队列描述符

●msg_ptr:指向缓冲区的指针

●msg_len:缓冲区大小

●msg_prio:消息优先级

返回值:

​    ●成功返回0,open返回消息队列描述符,mq_receive返回写入成功字节数

​    ●失败返回-1

注意:

​        在编译时报undefined reference to mq_open、undefined reference to mq_close时,除了要包含头文件#include<mqueue> #include <fcntl>外,还需要加上编译选项-lrt

消息队列的关闭与删除

​        使用mq_close函数关闭消息队列,关闭后消息队列并不从系统中删除。一个进程结束,会自动调用关闭打开着的消息队列。

​        使用mq_unlink 函数删除一个消息队列名,使当前进程无法使用该消息队列。并将队列标记为在所有进程关闭该队列后删除该队列。

​        Posix消息队列具备随内核的持续性。所以即使当前没有进程打开着某个消息队列,该队列及其上的消息也将一直存在,直到调用mq_unlink并最后一个进程关闭该消息队列时,将会被删除。操作系统会维护一个消息队列的引用计数,记录有多少进程正在使用该消息队列。只有当引用计数减为零时,消息队列才会被真正删除。

消息队列属性

每个消息队列有四个属性:

​    ●mq_getattr返回所有这些属性

​    ●mq_setattr 设置mg_flags属性,其他成员忽略

​    ●mq_open可以指定mq_maxmsg和mq_msgsize 属性,其他成员忽略

mq_attr结构:

struct mq_attr
{
	long mg_flags;//是否阻塞
	long mq_maxmsg;//最大消息数
	long mq_msgsize;//最大消息大小
	long mq_curmsgs;//当前消息的个数
};

使用消息队列通信

头文件:

#include<fcntl.h>
#include<sys/stat.h>
#include<mqueue.h>
#include<stdlib.h>

wmsg.c:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<stdlib.h>
#include<mqueue.h>

int main(int argc, char* argv[])
{
        int mqd = mq_open("/mymsg", O_WRONLY | O_CREAT, 0644, NULL);
        if(mqd == -1){
                perror("mq_open error");
                return -1;
        }
        //atoi:字符串转整形
        int ret = mq_send(mqd, argv[1], strlen(argv[1]), atoi(argv[2]));
        if(ret == -1){
                perror("mq_send error");
                exit(1);
        }
        return 0;
}

rmsg.c

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<stdlib.h>
#include<mqueue.h>

int main(int argc, char* argv[])
{
        int mqd = mq_open("/mymsg", O_RDONLY);
        if(mqd == -1){
                perror("mq_open error");
                return -1;
        }
        char buf[8192];
        unsigned int prio;
        int ret = mq_receive(mqd, buf, sizeof(buf), &prio);
        if(ret == -1){
                perror("mq_send error");
                exit(1);
        }
        printf("buf = %s, prio = %d\n", buf, prio);
        return 0;
}

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<stdlib.h>
#include<mqueue.h>

int main(int argc, char* argv[])
{
        int mqd = mq_open("/mymsg", O_RDONLY);
        if(mqd == -1){
                perror("mq_open error");
                return -1;
        }
        struct mq_attr attr;
        mq_getattr(mqd, &attr);
        //long类型是%lu
        printf("mq_curmsgs = %lu\n", attr.mq_curmsgs);//当前消息
        printf("mq_maxmsg = %lu\n", attr.mq_maxmsg);
        printf("mq_msgsize = %lu\n", attr.mq_msgsize);
        return 0;
}

最后阻塞了

多进程:成本高、进程不共享。因为每一个进程都是独立的虚拟内存。

想要解决这个问题就引出了线程。创建进程的目的是同时处理一些问题,不让它阻塞掉。

多线程可以解决对进程出现的问题。

cpu的执行单位是线程。

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值