进程间通信简称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的执行单位是线程。