(六) 进程间通信

进程间通信(IPC)

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

进程间通信方式:

1.        管道(pipe)和命名管道(FIFO)(最古老的IPC,但目前很少使用)

2.        信号(signal)

3.        消息队列(重点)

4.        共享内存

5.        信号量

6.        套接字(socket)

 

A.       管道通信:单向的、先进先出的

无名管道用于父进程和子进程件的通信,命名管道用于运行于同一系统中的任意两个进程间的通信。

无名管道由pipe() 函数创建:

int pipe ( int filedes [2]);

当一个管道建立时,它会创建两个文件描述符:

filedes[0] 用于读管道, filedes[1]用于写管道。

父进程fork一个子进程,子进程会继承父进程创建的管道。必须在fork()前调用pipe(),否则子进程不会继承文件描述符。

命名管道由mkfifo()函数创建:

int mkfifo(const char *pathname ,mode_t mode )

pathname: FIFO文件名

mode:属性

一旦创建了一个FIFO,就可以用open打开它,一般的文件访问函数(close,read,write等)都可用于FIFO。

**管道文件不存储数据,只是倒一下数据。**

 

B.       信号通信

信号处理:

1.        忽略此信号:大多数信号都按这种方式处理,有两种信号SIGKILL和SIGSTOP不能被忽略,因为它们向超级用户提供了一种终止或停止进程的方法。

2.        执行用户希望的动作:通知内核在某种信号发生时,调用一个用户函数。

3.        执行系统默认动作:对大多数信号的系统默认动作是终止该进程。

发送信号的主要函数有kill和raise。

区别:kill既可以向自身发送信号,也可以向其它进程发送信号。raise函数只能向进程自身发送信号。

int kill (pid_t pid, intsigno)

        int raise(int signo )

使用alarm函数可以设置一个时间值,产生SIGALRM信号

unsigned int alarm (unsigedint seconds)

pause函数使调用者进程挂起直至捕捉到一个信号。

int pause (void);

只有执行了一个信号处理函数后,挂起才结束。

信号处理的方式主要有两种,一种是使用简单的signal函数,另一种是使用信号集。

typedef void(*sighandler_t)(int );

       sighandler_t signal (intsignum, sighandler_t handler);

 

**共享内存、消息队列和信号量集都是XSI IPC,遵循相同的规范,因此编程有很多共性的地方。XSI IPC的共性:

1.       创建/获取 IPC结构,必须先提供一个外部的key。

2.       每个IPC结构都有一个唯一的ID与之对应,用key可以拿到id。

3.       外部key的类型是key_t,获得key的方式有三种:

a)       使用宏 IPC_PRIVATE 做key,这个key基本不用,因为这个key只能创建,不能获取。(有但不使用)

b)       可以用函数ftok()创建key。

c)       可以在一个公共的头文件中定义每个使用的key,key本身就是一个整数。

4.       函数 xxxget() 可以用key创建/获得 ID,比如:

        shmget() / msgget()

5.       每种IPC结构都提供了一个 xxxctl()函数,可以修改、删除、查询IPC结构。 

6.       使用key新建IPC结构时,参数flg一般都是IPC_CREAT|权限。

7.       xxxctl()函数中,cmd支持以下宏:

        IPC_STAT : 用于查询

       IPC_SET :   用于修改

      IPC_RMID : 用于删除

8.       XSI IPC 为每个IPC结构设置了一个ipc_perm 权限结构,通过xxxctl 函数 , 

可以修改uid,gid,mode字段,结构其它成员不能修改。

 

IPC结构可以使用命令 查看或者删除:

ipcs - 可以查询IPC

ipcrm - 可以删除IPC

 ipcs-a  查看所有IPC

                -m查看共享内存

                -q  查看消息队列

                -s  查看信号量集

ipcrm 删除时,需要提供 IPC的ID。

 

C.    共享内存

多个进程共享的一部分物理内存,是进程间共享数据的一种最快方法,一个进程向共享内存区写入数据,共享这个内存区域的其他所有进程就可以立刻看到其中的内容。映射物理内存叫挂接,用完以后解除映射叫脱接。

共享内存实现的步骤:

1.       先获得key,可以使用头文件或者 ftok()

2.       用key获得/创建一个共享内存的ID,函数shmget()。

3.       映射共享内存,挂接,函数shmat()。

4.       数据交互。IPC

5.       解除映射,脱接,函数shmdt()。

6.       如果确定共享内部不再使用,可以使用shmctl()函数删除。

ftok() 通过一个真实存在的路径 + 项目ID(0-255)生成一个key。

        key_tftok( const char * path, int id)   项目名(不为0即可)

**使用同一项目ID,对于不同文件的两个路径可能产生相同的键。**

int shmget(key_t key,size_t size,int flag)

flag新建时给IPC_CREAT|权限,获取时 0 。返回共享内存的ID,失败返回-1。

**共享内存的缺点是多个进程同时写的时候,数据完全混乱了。**

 

D.    消息队列(重点)

消息队列设计更加的合理,先把数据封入消息中,把消息存入队列。进程可以按照一定的规则添加新消息;另一些进程可以从消息队列中读走消息。消息队列也是采用内存做 交互媒介,系统内核管理一个队列,队列中存放着数据。

目前主要有两种类型的消息队列:

POSIX消息队列和系统V消息队列,系统V消息队列目前被大量使用。系统V消息队列是随内核持续的,只有内核重启或者人工删除时,该消息队列才会被删除。

消息队列的使用步骤:

1.       用ftok()获得外部的key。

2.       用msgget() 创建/获取 消息队列的ID。

3.       放入数据(msgsnd()) 或者 取出数据(msgrcv())。

4.       如果确定不再使用消息队列,用msgctl()删除消息队列。

IPC_CREAT 创建新的消息队列;IPC_EXCL与IPC_CREAT一同使用表示如果要创建的消息队列已经存在,则返回错误。IPC_NOWAIT读写消息队列无法满足时,不阻塞。

消息队列的正规用法:

消息分为 有类型消息和无类型消息,无类型消息编程比较简单,但接收数据时无法细分,只能先入先出。有类型消息编程比较规范,接收数据时可以区分,但不是先入先出。

   消息是一个结构,格式如下:

    struct 结构名{ //结构名程序员可以自定义

      longmtype; //消息类型, 消息类型>0,第一个成员必须是消息类型

      char mtext[1];//消息数据的首地址,数据区,支持任意类型的数据

    };

 

int msgsnd(int msgid,void* addr,size_tsize,int flag)

参数:msgid 就是消息队列的ID,用key可以获得。

addr是消息的首地址,也就是消息类型的首地址

size 是消息中 数据区的大小,不算类型。(算类型也可以)

flag 可以为 0 代表阻塞, IPC_NOWAIT 非阻塞(满了直接返回-1)

    

ssize_t msgrcv(int msgid,void* addr,size_tsize,int msgtype, int flag)

参数:msgid和addr与msgsnd一样,size是接收buffer的大小,

            flag和msgsnd 一样

            msgtype 决定了接收 何种类型的消息(消息类型必须大于0)

            > 0 接收特定类型的消息

            0    接受 任意类型的消息

          < 0 接收类型小于等于msgtype绝对值的消息,从小到大

成功返回实际接收到的字节数,失败返回 -1 。

函数msgctl(msgid,IPC_RMID,0) 可以删除消息队列。

**消息队列的删除和共享内存的删除机制不同,共享内存的删除只是做了个删除标记,不确保马上删除,只有挂接数为0的才能被删除。消息队列随时可以删除,即使队列中还有消息依然会被删除。**

 

E.     信号量

与其它进程间通信不大相同,主要用途是保护临界资源。进程可以根据它判断是否能够访问某些共享资源。处了用于访问控制外,还可以用于进程同步。

信号量分类:

二值信号量:信号量值只能取0或1,类似于互斥锁。但两者有不同:信号量强调共享资源,只要共享资源可用,其他进程同样可以修改信号量的值;互斥锁更强调进程。

计数信号量:信号量的值可以取任意非负值。

创建和打开:

int semget ( key_tkey, int nsems , int semflg );

key: 键值,由ftok获得;nsems: 指定打开或者创建的信号量集中信号量的数目;

       int semop (int semid , struct sembuf*sops, unsigned nspos );

功能:对信号量进行控制。

Semid: 信号量集的ID;sops:s 操作数组,表明要进行什么操作;nsops:元素个数

       struct sembuf {

       unsigned short sem_num;

       short sem_op;

       short sem_flg;

       }

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值