绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
[内核模块] 用户空间内核空间ipc总结(sockopt,ioctl,mmap,netlink,pr
2020-05-19 17:40:28

用户空间内核空间ipc总结(sockopt,ioctl,mmap,netlink,proc,seq,file,copy_user)

原作者:ubuntuer


多数的 Linux 内核态程序都需要和用户空间的进程交换数据,但 Linux 内核态无法对传统的 Linux 进程间同步和通信的方法提供足够的支持!本文就总结下常见的ipc,
getsockopt/setsockopt mmap netlink/socket proc/seq copy_from_user/copy_to_user 文件。采用先讲解后测试代码的方式,netlink和proc由于江哥和段兄都写的比较好了我就贴了链接... 好了不废话了开始

一.getsockopt/setsockopt
近看ebtables源码,发现与内核的ipc是采用的getsockopt, 具体实现是在内核中用nf_register_sockopt函数注册一个nf_sockopt_ops的结构体,比如说:



static struct nf_sockopt_ops nso = {
.pf = PF_INET, // 协议族
.set_optmin = 常数, // 定义小set命令字
.set_optmax = 常数+N, // 定义大set命令字
.set = do_nso_set, // 定义set处理函数
.get_optmin = 常数, // 定义小get命令字
.get_optmax = 常数+N, // 定义大get命令字
.get = do_nso_get, // 定义set处理函数
};

复制代码


其中命令字不能与系统已有的命令字重复。set/get处理函数是直接由用户空间的set/getsockopt函数调用的。


从这个图里面可以看出来,这种方法的本质就是调用是copy_from_user()/copy_to_user()方法完成内核和用户通信的,这样其实效率不高,多用在传递控制选项信息,不适合用做大量数据的传输。copy_from_user()/copy_to_user()我讲在后面介绍... 当然对于linux任何都是文件那么我想应该也是可以定义自己的ioctl的,这个在后面的
copy_xx_user的块设备中讲解
setsockopt/getsockopt kernel部分代码:



static int recv_msg(struct sock *sk, int cmd, void *user, unsigned int len)
{
int ret = 0;
printk(KERN_INFO "sockopt: recv_msg()/n");
/*
switch(cmd)
{
case IMP1_SET:
{
char umsg[64];
memset(umsg, 0, sizeof(char)*64);
copy_from_user(umsg, user, sizeof(char)*64);
printk("umsg: %s", umsg);
}
break;
}
*/
if (cmd == SOCKET_OPS_SET)
{
char umsg[64];
int len = sizeof(char)*64;
memset(umsg, 0, len);
ret = copy_from_user(umsg, user, len);
printk("recv_msg: umsg = %s. ret = %d/n", umsg, ret);
}
return 0;
}
static int send_msg(struct sock *sk, int cmd, void *user, int *len)
{
int ret = 0;
printk(KERN_INFO "sockopt: send_msg()/n");
if (cmd == SOCKET_OPS_GET)
{
ret = copy_to_user(user, KMSG, KMSG_LEN);
printk("send_msg: umsg = %s. ret = %d. success/n", KMSG, ret);
}
return 0;
}
static struct nf_sockopt_ops test_sockops =
{
.pf = PF_INET,
.set_optmin = SOCKET_OPS_SET,
.set_optmax = SOCKET_OPS_MAX,
.set = recv_msg,
.get_optmin = SOCKET_OPS_GET,
.get_optmax = SOCKET_OPS_MAX,
.get = send_msg,
};


复制代码


setsockopt/getsockopt user部分代码:


/*call function recv_msg()*/
ret = setsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_SET, UMSG, UMSG_LEN);
printf("setsockopt: ret = %d. msg = %s/n", ret, UMSG);
len = sizeof(char)*64;
/*call function send_msg()*/
ret = getsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_GET, kmsg, &len);
printf("getsockopt: ret = %d. msg = %s/n", ret, kmsg);
if (ret != 0)
{
printf("getsockopt error: errno = %d, errstr = %s/n", errno, strerror(errno));
}

复制代码



二. mmap共享内存
采用共享内存通信的一个显而易 见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而 共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就 解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存 中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的.
kernel:


#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/*.h> /* size_t */
#include <linux/mm.h>
#include <linux/kdev_t.h>
#include <asm/page.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gfp.h>
static unsigned char *myaddr=NULL;
static int simple_major = 0;
module_param(simple_major, int, 0);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kenthy@163.com.");
MODULE_DESCRIPTION("Kernel study and test.");
/*
* Common VMA ops.
*/
void simple_vma_open(struct vm_area_struct *vma)
{
printk(KERN_NOTICE "Simple VMA open, virt %lx, phys %lx/n",
vma->vm_start, vma->vm_pgoff << PAGE_SHIFT);
}
void simple_vma_close(struct vm_area_struct *vma)
{
printk(KERN_NOTICE "Simple VMA close./n");
}
struct page *simple_vma_nopage(struct vm_area_struct *vma,
unsigned long address, int *type)
{
struct page *pageptr;
unsigned long offset = (address - vma->vm_start);
if (offset>PAGE_SIZE*2)
{
printk("out of size/n");
return NULL;
}
printk("in vma_nopage: offset=%u/n", offset);
if(offset<PAGE_SIZE) // the first page
pageptr=virt_to_page(myaddr);
else // the second page
pageptr=virt_to_page(myaddr+PAGE_SIZE);
get_page(pageptr);
return pageptr;
}
static struct vm_operations_struct simple_nopage_vm_ops = {
.open = simple_vma_open,
.close = simple_vma_close,
.nopage = simple_vma_nopage,
};
static int simple_open (struct inode *inode, struct file *filp)
{
return 0;
}
static int simple_release(struct inode *inode, struct file *filp)
{
return 0;
}
static int simple_nopage_mmap(struct file *filp, struct vm_area_struct *vma)
{
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
printk("enter simple_nopage_mmap: offset=%u, vma->vm_pgoff=%u/n", offset, vma->vm_pgoff);
if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC))
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;
vma->vm_ops = &simple_nopage_vm_ops;
simple_vma_open(vma);
return 0;
}
/*
* Set up the cdev structure for a device.
*/
static void simple_setup_cdev(struct cdev *dev, int minor,
struct file_operations *fops)
{
int err, devno = MKDEV(simple_major, minor);
cdev_init(dev, fops);
dev->owner = THIS_MODULE;
dev->ops = fops;
err = cdev_add (dev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk (KERN_NOTICE "Error %d adding simple%d", err, minor);
}
static struct file_operations simple_nopage_ops = {
.owner = THIS_MODULE,
.open = simple_open,
.release = simple_release,
.mmap = simple_nopage_mmap,
};
/*
* We export two simple devices. There's no need for us to maintain any
* special housekeeping info, so we just deal with raw cdevs.
*/
static struct cdev SimpleDevs;
/*
* Module housekeeping.
*/
static int simple_init(void)
{
int result;
//unsigned int addr1, addr2;
dev_t dev = MKDEV(simple_major, 0);
/* Figure out our device number. */
if (simple_major)
result = register_chrdev_region(dev, 1, "simple_nopage");
else {
result = alloc_chrdev_region(&dev, 0, 1, "simple_nopage");
simple_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "simple_nopage: unable to get major %d/n", simple_major);
return result;
}
if (simple_major == 0)
simple_major = result;
/* Now set up two cdevs. */
simple_setup_cdev(&SimpleDevs, 0, &simple_nopage_ops);
myaddr = __get_free_pages(GFP_KERNEL, 1);
if (!myaddr)
return -ENOMEM;
// for test
strcpy(myaddr, "1234567890");
strcpy(myaddr+PAGE_SIZE, "abcdefghij");
return 0;
}
static void simple_cleanup(void)
{
cdev_del(&SimpleDevs);
unregister_chrdev_region(MKDEV(simple_major, 0), 1);
}
module_init(simple_init);
module_exit(simple_cleanup);


复制代码


user:


#include </work/apue/ourhdr.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char *argv[])
{
int fdin, fdout;
void *src, *dst;
struct stat statbuf;
unsigned char sz[1024]={0};
if ((fdin = open("/dev/simple_nopage", O_RDONLY)) < 0)
err_sys("can't open /dev/simple_nopage for reading");
if ((src = mmap(NULL, 4096*2, PROT_READ, MAP_SHARED,
fdin, 0)) == MAP_FAILED)
err_sys("mmap error for simplen");
memcpy(sz, src, 11);
sz[10]='/0';
printf("%x/n", src);
printf("%s/n/n", sz);
memcpy(sz, src+4096, 11);
printf("%x/n", src+4096);
printf("%s/n", sz);
exit(0);
}

复制代码

mmap加载文件后注意还要mknod


文章来源CU社区:[内核模块] 用户空间内核空间ipc总结(sockopt,ioctl,mmap,netlink,proc,seq,file,copy_user)

分享好友

分享这个小栈给你的朋友们,一起进步吧。

内核源码
创建时间:2020-05-18 13:36:55
内核源码精华帖内容汇总
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 飘絮絮絮丶
    专家
戳我,来吐槽~