perrynzhou

专注于系统组件研发

0%

进程间实时通信模块设计和实现

进程间实时通信模块设计和实现

介绍

  • 基于设备文件作为数据传输介质,一切皆文件
  • 设备文件必须可以读可以写
  • 通过linux kernel的file_operations来实现设备的读写

原理

  • 设计一个内核模块,客户端A写数据客户端B,客户端A到某个字符设备,然后通过设计的内核模块主动推送客户端B,这样就能达到进程间实时通信的效果.
  • 设备文件也是文件,通过绑定内核的file_operations的函数实现设备的open/release/read/write/mmap等接口,客户端当open/read/write时候会对应调用内核模块的open/read/write函数

实现

  • 进程A写数据
1
echo "aaaa" > /dev/memchan
  • 进程B接收数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*************************************************************************
> File Name: mem_channel_reader.c
> Author:perrynzhou
> Mail:perrynzhou@gmail.com
> Created Time: Fri 26 Jun 2020 01:35:15 PM CST
************************************************************************/

#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#define BUFFER_LENGTH 128
static const char *device_name = "/dev/memchan";
int main(void)
{

int fd = open(device_name, O_RDWR);
if (fd < 0)
{
fprintf(stdout, "%s,err:%s\n", device_name, strerror(errno));
return -1;
}
char *buffer = (char *)malloc(BUFFER_LENGTH);
memset(buffer, 0, BUFFER_LENGTH);

char *start = mmap(NULL, BUFFER_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

fd_set rds;

FD_ZERO(&rds);
FD_SET(fd, &rds);

while (1)
{
int ret = select(fd + 1, &rds, NULL, NULL, NULL);
if (ret < 0)
{
printf("select error\n");
exit(1);
}
if (FD_ISSET(fd, &rds))
{
#if 0
strcpy(buffer, start);
printf("ntychannel: %s\n", buffer);
#else
read(fd, buffer, BUFFER_LENGTH);
printf("channel: %s\n", buffer);
#endif
}
}

munmap(start, BUFFER_LENGTH);
free(buffer);
close(fd);

return 0;
}
  • 内核模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/*************************************************************************
> File Name: mem_channel.c
> Author:perrynzhou
> Mail:perrynzhou@gmail.com
> Created Time: Fri 26 Jun 2020 12:59:42 PM CST
************************************************************************/

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/poll.h>

// first device id in kernel
#ifndef MEM_CHANNEL_MAJOR
#define MEM_CHANNEL_MAJOR 110
#endif

//second device id in kernel
#ifndef MEM_CHANNEL_MINOR
#define MEM_CHANNEL_MINOR 2
#endif

#ifndef MEM_CHANNEL_DATA_LENGTH
#define MEM_CHANNEL_DATA_LENGTH 4096
#endif

#define ENABLE_POLL 1
#if ENABLE_POLL
uint8_t is_have_data = 0;
#endif
static const char *mem_channel_device = "memchan";
static struct cdev mem_channel_dev;
struct mem_channel
{
char *data;
size_t size;
#if ENABLE_POLL
wait_queue_head_t queue;
#endif
};
static struct mem_channel *chan;
int mem_channel_open(struct inode *node, struct file *pfile)
{
struct mem_channel *mem = NULL;
int num = node->i_rdev;
if (num > MEM_CHANNEL_MINOR || num == 0)
{
return -ENODEV;
}
mem = &chan[num];
pfile->private_data = mem;
printk(KERN_INFO "mem_channel_open %d device success\n", num);
return 0;
}
int mem_channel_release(struct inode *node, struct file *pfile)
{
return 0;
}
ssize_t mem_channel_read(struct file *pfile, char __user *buffer, size_t size, loff_t *ppos)
{
int ret;
unsigned long p = *ppos;
unsigned int count = size;
struct mem_channel *mem = pfile->private_data;
if (p > MEM_CHANNEL_DATA_LENGTH)
{
return 0;
}
if (count > (MEM_CHANNEL_DATA_LENGTH - p))
{
count = MEM_CHANNEL_DATA_LENGTH - p;
}
#if ENABLE_POLL
while (!is_have_data)
{
if (pfile->f_flags & O_NONBLOCK)
{
return -EAGAIN;
}
wait_event_interruptible(mem->queue, is_have_data);
}
#endif
if (copy_to_user(buffer, (void *)(mem->data + p), count))
{
ret = -EFAULT;
}
else
{
ret = strlen(buffer);
mem->size -= ret;
}
printk(KERN_INFO "read %d bytes from %ld\n", ret, p);
return ret;
}
ssize_t mem_channel_write(struct file *pfile, const char __user *buffer, size_t size, loff_t *ppos)
{
int ret;
unsigned long p = *ppos;
unsigned int count = size;
struct mem_channel *mem = (struct mem_channel *)pfile->private_data;
if (p > MEM_CHANNEL_DATA_LENGTH)
{
return 0;
}
if (count > (MEM_CHANNEL_DATA_LENGTH - p))
{
count = MEM_CHANNEL_DATA_LENGTH - p;
}
if (copy_from_user((void *)(mem->data + p), buffer, count))
{
ret = -EFAULT;
}
else
{
*ppos += count;
ret = count;
mem->size += count;
*(mem->data + p + count) = '\0';
printk(KERN_INFO "write %d bytes from %ld\n", count, p);
}
#if ENABLE_POLL
is_have_data = 1;
wake_up(&mem->queue);
#endif
return ret;
}

#if ENABLE_POLL
unsigned int mem_channel_poll(struct file *pfile, struct poll_table_struct *wait)
{

struct mem_channel *mem = pfile->private_data;
unsigned int mask = 0;

poll_wait(pfile, &mem->queue, wait);

if (is_have_data)
{
mask |= (POLLIN | POLLRDNORM);
}

return mask;
}
#endif
int mem_channel_mmap(struct file *pfile, struct vm_area_struct *vma)
{
struct mem_channel *mem = pfile->private_data;

vma->vm_flags |= VM_IO;
vma->vm_flags |= (VM_DONTEXPAND | VM_DONTDUMP);

if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(mem->data) >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot))
{
return -EAGAIN;
}
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = mem_channel_open,
.release = mem_channel_release,
.read = mem_channel_read,
.write = mem_channel_write,
.poll = mem_channel_poll,
.mmap = mem_channel_mmap,
};
//when execute insmod xx.ko,mem_channel_init will be called
static int mem_channel_init(void)
{
int result;
int i = 0;
//init first device no
dev_t devno = MKDEV(MEM_CHANNEL_MAJOR, 0);
//init minor device array for first device
result = register_chrdev_region(devno, MEM_CHANNEL_MINOR, mem_channel_device);
if (result != 0)
{
return result;
}
cdev_init(&mem_channel_dev, &fops);
mem_channel_dev.owner = THIS_MODULE;
//add device to kernel device list,that save data
cdev_add(&mem_channel_dev, devno, MEM_CHANNEL_MINOR);
chan = kmalloc(MEM_CHANNEL_MINOR * sizeof(struct mem_channel), GFP_KERNEL);
if (chan == NULL)
{
result = -ENOMEM;
goto failed;
}
memset(chan, 0, sizeof(struct mem_channel) * MEM_CHANNEL_MINOR);
for (; i < MEM_CHANNEL_MINOR; i++)
{
chan[i].size = MEM_CHANNEL_DATA_LENGTH;
chan[i].data = kmalloc(MEM_CHANNEL_DATA_LENGTH * sizeof(char), GFP_KERNEL);
if (chan[i].data == NULL)
{
goto failed;
}
memset(chan[i].data, 0, MEM_CHANNEL_DATA_LENGTH);
}
printk(KERN_INFO "mem_channel init success\n");
return 0;
failed:
unregister_chrdev_region(devno, MEM_CHANNEL_MINOR);
for (i = 0; i < MEM_CHANNEL_MINOR; i++)
{
if (chan[i].data != NULL)
{
kfree(chan[i].data);
chan[i].data = NULL;
}
chan[i].size = 0;
}
if (chan != NULL)
{
kfree(chan);
chan = NULL;
}
return result;
}
//when rmmod xx.ko,mem_channel_exit will be called
static void mem_channel_exit(void)
{
int i;
cdev_del(&mem_channel_dev);
for (i = 0; i < MEM_CHANNEL_MINOR; i++)
{

kfree(chan[i].data);
chan[i].data = NULL;
chan[i].size = 0;
}
if (chan != NULL)
{
kfree(chan);
chan = NULL;
}
unregister_chrdev_region(MKDEV(MEM_CHANNEL_MAJOR, 0), MEM_CHANNEL_MINOR);
printk(KERN_INFO "mem_channel_exit succes\n");
}

MODULE_AUTHOR("perrynzhou@gmail.com");
MODULE_LICENSE("GPL");

module_init(mem_channel_init);
module_exit(mem_channel_exit);
  • Makefile
1
2
3
4
5
6
7
8
9
10
11
12
KERNEL_SRC = /usr/src/kernels/3.10.0-1127.8.2.el7.x86_64
obj-m := mem_channel.o
module-objs := mem_channel.o

all:
$(MAKE) -C $(KERNEL_SRC) M=$(PWD) modules
gcc -g -std=gnu99 mem_channel_reader.c -o mem_channel_reader
clean:
rm *.ko *.o
rm -rf mem_channel_reader
rm -rf .mem_channel*
rm -rf modules.order Module.symvers