管道模型
由于管道传递数据的单向性,管道又称为半双工管道
管道的这一特点决定了其使用的局限性,具有以下特点:
- 数据只能由一个进程流向另一个进程(其中一个读管道,一个写管道);如果要进行双工通信,需要建 立两个管道。
- 管道只能用于父子进程或者兄弟进程间通信。,也就是说管道只能用于具有亲缘关系的进程间通信。
管道分为两种类型:匿名管道、命名管道
匿名管道
意思就是这个类型的管道没有名字,用完了就销毁了。
Linux 命令:
ps -ef | grep 关键字
这里面的竖线“|”就是一个管道。它会将前一个命令的输出,作为后一个命令的输入。
命名管道
这个类型的管道需要通过 mkfifo 命令显式地创建。
mkfifo hello
hello 就是这个管道的名称。管道以文件的形式存在。这个文件的类型是 p,就是 pipe 的意思
往管道里面写入东西
# echo "hello world" > hello
管道里面的内容没有被读出,这个命令就是停在这里的
读取管道里面的内容
# cat < hello
hello world
一方面,管道里面的内容被读取出来,打印到了终端上;另一方面,echo 那个命令正常退出了
消息队列模型
管道将信息一股脑儿地从一个进程,倒给另一个进程不同,消息队列有点儿像邮件,发送数据时,会分成一个一个独立的数据单元,也就是消息体,每个消息体都是固定大小的存储块,在字节流上不连续。
msgget 函数
创建一个消息队列,参数 key,这是消息队列的唯一标识。
如何保持唯一性呢?
可以指定一个文件,ftok 会根据这个文件的 inode,生成一个近乎唯一的 key。只要在这个消息队列的生命周期内,这个文件不要被删除就可以了。只要不删除,无论什么时刻,再调用 ftok,也会得到同样的 key。
eg:
code来自 极客时间趣谈linux操作系统
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
int main() {
int messagequeueid;
key_t key;
if((key = ftok("./1234", 1024)) < 0)
{
perror("ftok error");
exit(1);
}
printf("Message Queue key: %d.\n", key);
if ((messagequeueid = msgget(key, IPC_CREAT|0777)) == -1)
{
perror("msgget error");
exit(1);
}
printf("Message queue id: %d.\n", messagequeueid);
}
System V IPC 体系有一个统一的命令行工具:ipcmk,ipcs 和 ipcrm 用于创建、查看和删除 IPC 对象。
ipcs -q 就能看到上面创建的消息队列对象
msgsnd 函数
发送信息,第一个参数是 message queue 的 id,第二个参数是消息的结构体,第三个参数是消息的长度,最后一个参数是 flag。IPC_NOWAIT 表示发送的时候不阻塞,直接返回。
eg:
code来自 极客时间趣谈linux操作系统
消息结构的定义
struct msg_buffer {
long mtype;
char mtext[1024];
};
类型 type 和正文 text 没有强制规定,只要消息的发送方和接收方约定好即可。
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <getopt.h>
#include <string.h>
struct msg_buffer {
long mtype;
char mtext[1024];
};
int main(int argc, char *argv[]) {
int next_option;
const char* const short_options = "i:t:m:";
const struct option long_options[] = {
{ "id", 1, NULL, 'i'},
{ "type", 1, NULL, 't'},
{ "message", 1, NULL, 'm'},
{ NULL, 0, NULL, 0 }
};
int messagequeueid = -1;
struct msg_buffer buffer;
buffer.mtype = -1;
int len = -1;
char * message = NULL;
do {
next_option = getopt_long (argc, argv, short_options, long_options, NULL);
switch (next_option)
{
case 'i':
messagequeueid = atoi(optarg);
break;
case 't':
buffer.mtype = atol(optarg);
break;
case 'm':
message = optarg;
len = strlen(message) + 1;
if (len > 1024) {
perror("message too long.");
exit(1);
}
memcpy(buffer.mtext, message, len);
break;
default:
break;
}
}while(next_option != -1);
if(messagequeueid != -1 && buffer.mtype != -1 && len != -1 && message != NULL){
if(msgsnd(messagequeueid, &buffer, len, IPC_NOWAIT) == -1){
perror("fail to send message.");
exit(1);
}
} else {
perror("arguments error");
}
return 0;
}
msgrcv 函数
收消息,第一个参数是 message queue 的 id,第二个参数是消息的结构体,第三个参数是可接受的最大长度,第四个参数是消息类型, 最后一个参数是 flag。IPC_NOWAIT 表示发送的时候不阻塞,直接返回。
eg:
code来自 极客时间趣谈linux操作系统
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <getopt.h>
#include <string.h>
struct msg_buffer {
long mtype;
char mtext[1024];
};
int main(int argc, char *argv[]) {
int next_option;
const char* const short_options = "i:t:";
const struct option long_options[] = {
{ "id", 1, NULL, 'i'},
{ "type", 1, NULL, 't'},
{ NULL, 0, NULL, 0 }
};
int messagequeueid = -1;
struct msg_buffer buffer;
long type = -1;
do {
next_option = getopt_long (argc, argv, short_options, long_options, NULL);
switch (next_option)
{
case 'i':
messagequeueid = atoi(optarg);
break;
case 't':
type = atol(optarg);
break;
default:
break;
}
}while(next_option != -1);
if(messagequeueid != -1 && type != -1){
if(msgrcv(messagequeueid, &buffer, 1024, type, IPC_NOWAIT) == -1){
perror("fail to recv message.");
exit(1);
}
printf("received message type : %d, text: %s.", buffer.mtype, buffer.mtext);
} else {
perror("arguments error");
}
return 0;
}
共享内存模型
共享内存,顾名思义就是允许两个不相关的进程访问同一个物理内存。共享内存并未提供同步机制。
shmget函数
创建一个共享内存
int shmget(key_t key, size_t size, int flag);
第一个参数是 key,和 msgget 里面的 key 一样,都是唯一定位一个共享内存对象,也可以通过关联文件的方式实现唯一性。第二个参数是共享内存的大小。第三个参数如果是 IPC_CREAT,同样表示创建一个新的。
eg:
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
int main() {
int sharememid;
key_t key;
if((key = ftok("./1234", 1024)) < 0)
{
perror("ftok error");
exit(1);
}
printf("Share Men key: %d.\n", key);
if ((sharememid = shmget(key, 1024, IPC_CREAT|0777)) == -1)
{
perror("shmget error");
exit(1);
}
printf("Share Men id: %d.\n", sharememid);
}
shmat 函数
如果一个进程想要访问这一段共享内存,需要将这个内存加载到自己的虚拟地址空间的某个位置,通过 shmat 函数,就是 attach 的意思。其中 addr 就是要指定 attach 到这个地方。
通常的做法是将 addr 设为 NULL,让内核选一个合适的地址。返回值就是真正被 attach 的地方。
void *shmat(int shm_id, const void *addr, int flag);
shmdt 函数
如果共享内存使用完毕,可以通过 shmdt 解除绑定,然后通过 shmctl,将 cmd 设置为 IPC_RMID,从而删除这个共享内存对象。
int shmdt(void *addr);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
信号量
使得同一个共享的资源,同时只能被一个进程访问。在 System V IPC 进程间通信机制体系中,有信号量(Semaphore)。因此,信号量和共享内存往往要配合使用。
信号量其实是一个计数器,主要用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
信号量初始化为一个数值,来代表某种资源的总体数量
两种原子操作
- P 操作
申请资源操作 - V 操作
归还资源操作
semget 函数
创建一个信号量
第一个参数 key 同上,第二个参数 num_sems 不是指资源的数量,而是表示可以创建多少个信号量,形成一组信号量,也就是说,如果你有多种资源需要管理,可以创建一个信号量组。
int semget(key_t key, int num_sems, int sem_flags);
semctl 函数
初始化信号量的总的资源数量,通过 semctl 函数。
第一个参数 semid 是这个信号量组的 id,第二个参数 semnum 才是在这个信号量组中某个信号量的 id,第三个参数是命令,如果是初始化,则用 SETVAL,第四个参数是一个 union。如果初始化,应该用里面的 val 设置资源总量。
int semctl(int semid, int semnum, int cmd, union semun args);
union semun
{
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
semop 函数
进行P 操作和V 操作
第一个参数还是信号量组的 id,一次可以操作多个信号量。第三个参数 numops 就是有多少个操作,第二个参数将这些操作放在一个数组中。
数组的每一项是一个 struct sembuf,里面的第一个成员是这个操作的对象是哪个信号量。第二个成员就是要对这个信号量做多少改变。当相应的资源数不能满足请求时,就要看 sem_flg ,
int semop(int semid, struct sembuf semoparray[], size_t numops);
struct sembuf
{
short sem_num; // 信号量组中对应的序号,0~sem_nums-1
short sem_op; // 信号量值在一次操作中的改变量
short sem_flg; // IPC_NOWAIT, SEM_UNDO
}
信号
Linux系统中信号没有特别复杂的数据结构,就是用一个代号一样的数字。
Linux 提供了几十种信号,分别代表不同的意义。信号之间依靠它们的值来区分。
信号可以在任何时候发送给某一进程,进程需要为这个信号配置信号处理函数。
参考资料:
趣谈Linux操作系统(极客时间)链接:
http://gk.link/a/10iXZ
欢迎大家来一起交流学习