linux下多进程多线程之三通信

本文将注重讲解linux下多进程多线程之三通信这块知识点.

线程间的通信:

因为线程之间是在同一个进程下创建的,所以大家共享同一类的全局变量,所以我们只需要用全局变量便可以轻松做到线程间的通信,或者在堆里面建造一个队列,用互斥锁去维护这个队列从而可做到线程间的通信。若有一些特殊要求的线程间通信,我们可采取条件变量的方式,向某些线程发送信号,告知已经满足某些条件了,可以执行某些特定的代码(可参看多进程多线程编程之同步里面的介绍)。

总之,线程间的通信,建议大家采取全局变量的模式.若有特殊需求可参考,消息队列,条件变量等

进程间的通信:

进程间的通信是我们需要掌握的重要知识点,因为很多的时候,我们提及到通信,大多还是涉及到进程间的。

我们主要讲解关于System V IPC 之间的进程间通信

总体概述有有名(匿名)管道,消息队列,共享内存,信号量,套接字。

关于套接字,读者可参看本人博客linux下的网络编程之socket,信号量参考多进程,多线程编程之同步。

我们主要讲解管道和共享内存,因为消息队列模式慢慢在编程通信中淡出。其实我们只要熟练掌握其中的一种,了解其它方式就可以了。

1,有名管道。

管道分为两种,一种是匿名管道,一种是有名管道。匿名管道主要是针对同一个程序下面的父子进程之间的通信。有名管道是多应用程序下的进程间的通信方式。

因为有名管道是在linux下面是一种文件的形式存在,所以我们更多的是利用创建这种文件和打开这种文件,读写这种文件的方式存在。

第一 创建一个有名管道,我们使用函数int mkfifo(const char * pathname,mode_t mode);

参数pathname是创建有名管道的文件路径,mode是创建的时候赋予有名管道的文件权限。两个非父子进程之间只需要一个进程使用此函数进行管道文件的创建,另一个进程进行使用这个文件。

第二,打开一个管道文件,我们使用函数int open(constchar* pathname,int flags);

参数pathname是打开管道文件使用的文件路径参数,flags是用什么方式打开这个文件。

我们利用open函数打开一个管道文件,返回一个文件描述符,我们就可以对这个文件描述符进行读写操作,注意一点的是,管道文件一个时候只接受一个数据的写入,然后除非数据被其它的进程给读取了,不然再次写入的时候,数据会被阻塞,写入不了。写有名管道数据的时候,有一个最大数据量写入数据,是根据"/usr/include/linux/limits.h"下面的PIPE_BUF限制的,就是一次最多只能往这里面写入PIPE_BUF这多数据,这样做的原因是为了保持有名管道的原子性操作,保证数据一次被全部取完。

当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;

 

2,匿名管道

顾名思义,匿名管道是没有名字的管道,只是在父子进程之间通过一种标示来标示这种匿名管道。

首先我们使用函数 int pipe(int filedes[2]);创建一个管道文件,用来进行父子进程之间的通信。这是一个拥有2个int类型的数组,其中filedes[0]是其读端,filedes[1]是其写端。调用函数pipe成功后,返回两个文件描述符,一个用来写一个用来读。因为我们是使用父子进程的方式,所以我们在调用pipe后,再调用fork,创建的是两个filedes数组,一个在父进程,一个在子进程。

调用pipe成功后,我们就产生了一个匿名的管道,因为也是文件描述符,所以在读写操作上,我们就只需要使用函数write和read就可以做到其通信。

例子:

 

#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

int main()
{
        int pipes[2];
        pid_t pid=0;
        if(pipe(pipes)==0)//创建一个匿名管道
        {
                pid=fork();//创建父子进程
                if(0==pid)
                {
                        char buf[256];
                        memset(buf,0,sizeof(buf));
                        strcpy(buf,"Hello pipe");
                        close(pipes[0]);//关闭读端,只使用写端
                        write(pipes[1],buf,strlen(buf));
                        exit(0);
                }
                else if(pid>0)//父进程,返回子进程pid
                {
                        char buf[256];
                        memset(buf,0,sizeof(buf));
                        close(pipes[1]);//关闭写端,只使用读端
                        read(pipes[0],buf,sizeof(buf));
                        printf("子进程传进来数据=%s\n",buf);
                        wait(pid);
                }
                else
                {
                        perror("fork:");
                }
        }
}


3 共享内存

使用消息队列时,一个进程要向队列中写入消息,这要引起从用户地址空间向内核地址空间的一次复制, 同样一个进程进行消息读取时也要进行一次复制。共享内存的优点是完全省去了这些操作. 共享内存会映射到进程的虚拟地址空间,进程对其可以直接访问,避免了数据的复制过程. 因此,共享内存是GNU/Linux现在可用的最快速的IPC机制。 进程退出时会自动和已经挂接的共享内存区段分离,但是仍建议当进程不再使用共享区段时调用shmdt来卸载区段。 

注意,当一个进程分支出父进程和子进程时,父进程先前创建的所有共享内存区段都会被子进程继承.  如果区段已经做了删除标记(在前面以IPC——RMID指令调用shmctl),而当前挂接数已经变为0,这个区段就会被移除。

首先创建一个共享区,我们是使用函数: int shmget(key_t key, size_t size, int shmflg);

参数1的key是用ftok函数生成的,ftok原型是key_t ftok( const char * fname, int id );第一个参数一个文件的路径,第二个参数是你随意指定的一个整型值.这样产生的返回值是文件的索引节点号加上你随意指定的一个整型值,比如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的整型值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。

参数2的size,是你指定的产生的共享内存大小,以字节为单位。

参数3的shmflg一般与创建的共享内存的权限做相与的操作,比如IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符。这里设置了共享内存的一些特性,比如权限,比如创建等
开始把共享内存映射到本进程的某一个内存的区域。void *shmat(int shmid, const void *shmaddr, int shmflg);

参数1是通过shmget返回的一个共享内存的标示符。

参数2是指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置。我们一般就指定为NULL;

参数3如果是SHM_RDONLY:为只读模式,其他为读写模式。

开始使用一个共享内存,我们使用函数int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数1shmid是通过之前调用的函数shmget得到的一个共享内存的标示。

参数2cmd是我们获取这个内存的时候,对这个内存的一些设置操作,如IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中。IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内。IPC_RMID:删除这片共享内存。

参数3buf是我们获取这块内存时候,指向这块内存的一个指针。这个时候我们就可以通过操作这个指针,而达到操作这个内存的目的。

开始断开与共享内存的访问,我们使用函数int shmdt(const void *shmaddr)。参数很明显,是我们通过函数shmctl获取的指向共享内存的指针.通过调用这个函数,我们就让本进程从当前的共享内存断开。

例子:创建一个共享内存并使用它

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdlib.h>
int main()
{
        key_t key=ftok(".",200);//创建一个标示符
        if(key==-1)perror("ftok:"),exit(-1);
        int shmid=shmget(key,4,0666|IPC_CREAT|IPC_EXCL);//创建一个共享内存,大小为4个字节
        if(shmid==-1)perror("shmget"),exit(-1);
        void*p=shmat(shmid,NULL,0);//挂载到这个共享内存上去
        if(p==(void*)-1)perror("shmat"),exit(-1);
        int *pi=p;//将共享内存转换我们需要使用的类型
        *pi=1000;//使用这个共享内存
        shmdt(p);
        return 0;
}

例子:使用一个创建好的共享内存

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdlib.h>
int main()
{
        key_t key=ftok(".",200);//创建一个标示符,因为这里我们需要是使用一个创建好的共享内存,所以这里的参数值应该和创建共享内存时候使用的参数一样,才能保证我们使用的是创建好的
        if(key==-1)perror("ftok:"),exit(-1);
        int shmid=shmget(key,0,0);//得到这个共享内存的标示,因为我们是使用这个共享内存,所以我们就只需要在函数的后面两个参数都赋上0
        if(shmid==-1)perror("shmget"),exit(-1);
        void*p=shmat(shmid,NULL,0);//挂载上这个共享内存,返回这个共享内存的首地址
        if(p==(void*)-1)perror("shmat"),exit(-1);
        int * pi=p;
        printf("%d\n",*pi);//打印出共享内存里面的值
        shmdt(p);
        return 0;
}


多进程的通信方式,建议使用共享内存或者管道来进行通信,消息队列不太推荐.我们只要掌握好其中的一种方式,知道其它方式就可以了。


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值