linux c ipc机制
ipc
ipc 意思就是 进程间通信机制的简称
在linux(centos)环境下 使用 ipcs
(信息查看),ipcrm
(删除), ipcmk
(创建)
通过指令 ipcs
查看, linux 支持的IPC机制有三种
- Message Queues: 消息队列
- Shared Memory Segments: 共享内存段
- Semaphore Arrays: 信号量数组
[root@process_comm#] ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
0x4201032a 0 root 666 184 2
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Semaphore Arrays --------
key semid owner perms nsems
消息队列
代码示例
代码如下: 使用 gcc -o recv ipc_que_recv.c
, gcc -o send ipc_que_send.c
编译各自代码得到 recv
和 send
,分别运行
ipc_que_recv.c
#include <stdio.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
int create_msg_queue() {
/**
* 该方法通过指定参数, 生成对应的 唯一key id标识
* extern key_t ftok (const char *__pathname, int __proj_id) __THROW;
* __pathname: 存在的路径名或者文件,必须存在,否则返回错误
* __proj_id: 自己约定, 在unix中取值 1~255
*/
key_t _key = ftok("temp113", 66);
if(_key < 0) {
printf("error to ftok \n");
return;
}
printf("ftok _key: 0x%0x \n", _key);
/**
* 该方法主要是为了根据 __key 查询消息队列是否已经创建
* extern int msgget (key_t __key, int __msgflg) __THROW;
* __key: 消息队列的对象的关键字(key)
* __msgflg: 对条件进行判断
* 0666: 第一位表示 条件判断的处理方法
* 第二位表示 当前用户的权限, 读.写.可执行
* 第三位表示 group用户组权限
* 第四位表示 其它用户的权限
* ret: -1 失败, 权限被拒绝
* 0 成功
* 详见: rttno-base.h
* /proc/sys/kernel/msgmax 一个消息的字节大小
* /proc/sys/kernel/msgmnb 存放消息队列的总字节数
* /proc/sys/kernel/msgmni 系统的消息队列数目
* 查看系统支持消息队列数量,
*/
// int msgid = msgget(_key, IPC_CREAT | IPC_EXCL | 0666);
int msgid = msgget(_key, IPC_CREAT | 0666);
if(msgid < 0)
{
printf("error msgid %d \n", msgid);
// errno 是记录系统的最后一次错误代码
printf("%d : %s\n", errno, strerror(errno));
return 1;
}
printf("msgget msgid: 0x%0x \n", msgid);
return msgid;
}
struct msgbuf
{
long mtype;
unsigned char buff[92];
};
int main(int argc, char const *argv[])
{
int msg_id = create_msg_queue();
struct msgbuf sbuf;
sbuf.mtype = 0x01;
int cnt = 0;
while(1) {
cnt ++;
memset(sbuf.buff, 0x0, sizeof(sbuf.buff));
/**
* 该函数为接收队列消息
* extern ssize_t msgrcv (int __msqid, void *__msgp, size_t __msgsz, long int __msgtyp, int __msgflg);
* __msqid: msg_id
* __msgp: 接收数据的缓冲区
* __msgsz: 接收数据缓冲区的大小
* __msgtyp: 接收数据的类型, 就是可以通过三四个以上的进程之间的通信,用类型表示要发送给指定的消费者
* 0, 函数不做类型检查返回队列中最旧的内容
* >0, 接收类型等于 __msgtyp 的第一个消息
* <0, 接收类型等于或者小于msgtyp绝对值的第一个消息
* __msgflg: 控制函数行为
* IPC_NOWAIT: 队列为空,返回 ENOMSG( No message of desired type), 并将控制权交回调用函数的进程, 如果不指定, 那函数将一直处于阻塞状态
* MSG_NOERROR: 如何函数取得的消息长度大于 __msgsz, 将只返回 msgsz长度的信息, 剩下的会被丢弃, 如果不指定这个函数, 将被返回状态码E2BIG(Argument list too long)
* IPC_EXCEPT: 与msgtype配合使用返回队列中第一个类型不为msgtype的消息
* ret: -1, 读取错误, 错误存在 error 中
* 否则返回实际读取的数据长度
*/
int rlen = msgrcv(msg_id, &sbuf, sizeof(sbuf.buff), 0x02, 0);
if(rlen < 0) {
printf("%d : %s", errno, strerror(errno));
return -1;
} else {
printf("[%d]%d:%0x %0x %0x %0x\n", cnt, rlen, sbuf.buff[0],sbuf.buff[20],sbuf.buff[90],sbuf.buff[91]);
}
usleep(1000*100);
}
return 0;
}
ipc_que_send.c
#include <stdio.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
int get_msg_queue() {
key_t _key = ftok("temp113", 66);
if(_key < 0)
{
printf("%d : %s",errno,strerror(errno));
return -1;
}
int msg_id = msgget(_key, IPC_CREAT);
printf("msgid: %d \n", msg_id);
return msg_id;
}
struct msgbuf
{
long mtype;
unsigned char buff[92];
};
int main(int argc, char const *argv[])
{
int msg_id = get_msg_queue();
struct msgbuf sbuf;
memset(sbuf.buff, 'c', sizeof(sbuf.buff));
unsigned char cnt = 0;
sbuf.mtype = 0x02;
sbuf.buff[0] = 0x0;
sbuf.buff[91] = 'a';
while (1)
{
sbuf.buff[0] = cnt;
cnt++;
/**
* 该函数为向队列发送数据
* extern int msgsnd (int __msqid, const void *__msgp, size_t __msgsz, int __msgflg);
* __msgflg: 控制发送函数的行为, IPC_NOWAIT,如果消息队列已满,消息将不被写入队列,控制权返回调用函数的线程。如果不指定这个参数,线程将被阻塞直到消息被可以被写入。
* ret: 0 成功
* -1 errno
*/
int res = msgsnd(msg_id, &sbuf, sizeof(sbuf.buff), 0);
printf("[%d] res %d, rlen %d res: %0x %0x %0x %0x \n", cnt, res, sizeof(sbuf.buff),sbuf.buff[0], sbuf.buff[20], sbuf.buff[60], sbuf.buff[91]);
sleep(1);
}
return 0;
}
实验探索
1.当消息队列一旦创建之后, 他是存在于内核系统的, 不限于用户态的程序, 也就是说用户态的程序不管是java还是c, 不管是运行还是不运行, 不管是两个进程还是更多的进程, 他都可以对消息队列进行收发数据的操作, 异或同一进程自发自收
linux 内核源码查看教程: https://www.cnblogs.com/han-guang-xue/p/16968708.html
2.消息队列在内核中的源码如下:
long do_msgsnd(int msqid, long mtype, void __user *mtext,
size_t msgsz, int msgflg)
{
struct msg_queue *msq;
struct msg_msg *msg;
...
msq = msq_obtain_object_check(ns, msqid); // 返回全局的队列
...
msg = load_msg(mtext, msgsz); //加载数据
if (IS_ERR(msg))
return PTR_ERR(msg);
...
主要查看 mtext
的走向 load_msg
, 看最终我们的数据到底存在什么地方,并如何传递的
struct msg_msg *load_msg(const void __user *src, size_t len)
{
...
msg = alloc_msg(len); //使用kmallc申请的内存
if (msg == NULL)
return ERR_PTR(-ENOMEM);
alen = min(len, DATALEN_MSG);
if (copy_from_user(msg + 1, src, alen)) //将数据拷贝到内核态申请的内存中
goto out_err;
for (seg = msg->next; seg != NULL; seg = seg->next) {
len -= alen;
src = (char __user *)src + alen;
alen = min(len, DATALEN_SEG);
if (copy_from_user(seg + 1, src, alen))
goto out_err;
}
...
结论:
存储方式和流程: 消息队列对数据的存储方式, 当给定一块数据块的时候, 内核代码回去创建一个大小为: sizeof(struct msg_msg)+len-8 的消息块, 并将数据保存在消息块msg_msg
中, 然后将消息块保存在全局队列 msg_queue
中, 自然的 msgrcv
也就是时对应从内核态拷贝到用户态,然后释放内核态的数据。
拷贝流程: 两次, 用户->copy->内核(内核申请内存), 内核->copy->用户(内核释放内存)
共享内存段
共享内存段通信不同于消息队列, 内核将一块内存共享出去,可以提供给多个进程访问
ipc_share_send.c
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <errno.h>
int get_msgid_share(int size) {
// temp114, 是当前的文件.需要手动创建, 如果不存在, 则返回失败
key_t _key = ftok("temp114", 66);
if(_key < 0)
{
printf("%d : %s",errno,strerror(errno));
return -1;
}
/**
* extern int shmget (key_t __key, size_t __size, int __shmflg) __THROW;
* __key: 共享内存段的 key
* __size: 需要共享的内存量
* __shmflg: 权限和行为
*/
int msg_id = shmget(_key, size, 666|IPC_CREAT);
printf("msgid: %d, key: %0x \n", msg_id, _key);
return msg_id;
}
int main(int argc, char const *argv[])
{
unsigned char sdata[40] = {0xAC};
int shmid = get_msgid_share(sizeof(sdata));
/**
* extern void *shmat (int __shmid, const void *__shmaddr, int __shmflg) __THROW;
* __shmaddr: 在进程中, 指向连接共享内存的地址, 0,NULL, 系统自动选择
* __shmflag: 权限和行为
* SHM_RDONLY: 只读
* SHM_RND: 如果__shmaddr 没有指定,则会连接到addr指定的地址上面
* SHM_REMAP: 若__shmaddr 已经被关联了,则重新关联
* SHM_EXEC: 指定对内存段的执行权限,可读可写
* ret: 返回共享内存在进程中的地址
*/
char *shmat_addr = shmat(shmid, NULL, SHM_EXEC);
printf("shmat_add: %p \n", shmat_addr);
unsigned char cnt = 0;
while (1)
{
sdata[0] = cnt++;
sdata[39] = cnt;
memcpy(shmat_addr, &sdata, sizeof(sdata));
printf("send times : %d \n", cnt);
sleep(1);
}
return 0;
}
ipc_share_recv.c
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <errno.h>
int get_msgid_share(int size) {
key_t _key = ftok("temp114", 66);
if(_key < 0)
{
printf("%d : %s",errno,strerror(errno));
return -1;
}
int msg_id = shmget(_key, size, 666|IPC_CREAT);
printf("msgid: %d, key: %0x \n", msg_id, _key);
return msg_id;
}
int main(int argc, char const *argv[])
{
unsigned char sdata[40] = { 0x00 };
int shmid = get_msgid_share(sizeof(sdata));
char *shmat_addr = shmat(shmid, NULL, SHM_RDONLY); //只读
printf("shmat_add: %p \n", shmat_addr);
unsigned char cnt = 0;
while (1)
{
memcpy(&sdata, shmat_addr, sizeof(sdata));
printf("recv[%d]: %0x %0x %0x %0x \n", cnt, sdata[0],sdata[39], sdata[10], sdata[20]);
sleep(1);
}
return 0;
}
代码如上, 分别运行如下指令并运行 send 和 recv
gcc -o send ipc_share_send.c
gcc -o recv ipc_share_recv.c
信号量
socket 编程
其中 AF_UNIX
就是实现类似于本地IPC通讯, Unix Domain Socket(简称 UDS)
和传统socket不同的是:
- 不需要经过网络协议栈
- 不需要打包拆包、计算校验和、维护序号和应答等,可靠性更强
- UNIX Domain Socket传输效率比通过loopback地址快将近一倍
- 不需要设置 ip端口等, 需要设置
struct sockaddr_un
中的sun_path
, 其为抽象目录, 在本地通讯中担任角色为域, 同一个域中可以相互之间收发数据struct sockaddr_un { sa_family_t sun_family; /*PF_UNIX或AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* 路径名 */ };
结论
同样的, 该方式不限于语言, 不限于进程数量, 相对于队列而言, 该方式效率搞,但是线程安全什么的都需要自己去控制了
本文来自博客园踩坑狭,作者:韩若明瞳,转载请注明原文链接:https://www.cnblogs.com/han-guang-xue/p/16968447.html