下面总结了7种方式,主要对以前不是很熟悉的方式做了编程实现,以便加深印象。
1.使用API:这是最常使用的一种方式了
A.get_user(x,ptr):在内核中被调用,获取用户空间指定地址的数值并保存到内核变量x中。
B.put_user(x,ptr):在内核中被调用,将内核空间的变量x的数值保存到到用户空间指定地址处。
C.Copy_from_user()/copy_to_user():主要应用于设备驱动读写函数中,通过系统调用触发。
2.使用proc文件系统:和sysfs文件系统类似,也可以作为内核空间和用户空间交互的手段。
/proc 文件系统是一种虚拟文件系统,通过他可以作为一种linux内核空间和用户空间的。与普通文件不同,这里的虚拟文件的内容都是动态创建的。
使用/proc文件系统的方式很简单。调用create_proc_entry,返回一个proc_dir_entry指针,然后去填充这个指针指向的结构就好了,我下面的这个测试用例只是填充了其中的read_proc属性。
下面是一个简单的测试用例,通过读虚拟出的文件可以得到内核空间传递过来的“proc ! test by qiankun!”字符串。
3.使用sysfs文件系统+kobject:其实这个以前是编程实现过得,但是那天太紧张忘记了,T_T。每个在内核中注册的kobject都对应着sysfs系统中的一个目录。可以通过读取根目录下的sys目录中的文件来获得相应的信息。除了sysfs文件系统和proc文件系统之外,一些其他的虚拟文件系统也能同样达到这个效果。
4.netlink:netlink socket提供了一组类似于BSD风格的API,用于用户态和内核态的IPC。相比于其他的用户态和内核态IPC机制,netlink有几个好处:1.使用自定义一种协议完成数据交换,不需要添加一个文件等。2.可以支持多点传送。3.支持内核先发起会话。4.异步通信,支持缓存机制。
对于用户空间,使用netlink比较简单,因为和使用socket非常的类似,下面说一下内核空间对netlink的使用,主要说一下最重要的create函数,函数原型如下:
extern struct sock *netlink_kernel_create(struct net *net,
int unit,unsigned int groups,
void (*input)(struct sk_buff *skb),
struct mutex *cb_mutex,
struct module *module);
第一个参数一般传入&init_net。
第二个参数指的是netlink的类型,系统定义了16个,我们如果使用的话最好自己定义。这个需和用户空间所使用的创建socket的第三个参数一致,才可以完成通信。
第四个参数指的是一个回调函数,当接受到一个消息的时候会调用这个函数。回调函数的参数为struct sk_buff类型的结构体。通过分析其结构成员可以得到传递过来的数据
第六个参数一般传入的是THIS_MODULE。指当前模块。
下面是对netlink的一个简单测试,将字符串“netlink test by qiankun”通过netlink输出到内核,内核再把字符串返回。Netlink类型使用的是22.
5.文件:应该说这是一种比较笨拙的做法,不过确实可以这样用。当处于内核空间的时候,直接操作文件,将想要传递的信息写入文件,然后用户空间可以读取这个文件便可以得到想要的数据了。下面是一个简单的测试程序,在内核态中,程序会向“/home/melody/str_from_kernel”文件中写入一条字符串,然后我们在用户态读取这个文件,就可以得到内核态传输过来的数据了。
6.使用mmap系统调用:可以将内核空间的地址映射到用户空间。在以前做嵌入式的时候用到几次。一方面可以在driver中修改Struct file_operations结构中的mmap函数指针来重新实现一个文件对应的映射操作。另一方面,也可以直接打开/dev/mem文件,把物理内存中的某一页映射到进程空间中的地址上。
其实,除了重写Struct file_operations中mmap函数,我们还可以重写其他的方法如ioctl等,来达到驱动内核空间和用户空间通信的方式。
【摘要】Linux中的内核空间到用户空间的地址映射让用户层应用可以直接访问内核地址,这就是mmap方法。应用程序通过内存映射可以直接访问设备的I/O存储区或DMA缓冲。内存映射使用户空间的一段地址关联到设备内存上,程序在映射的地址范围内进行读取或者写入,实际上就是对设备的访问。
struct file_operations simple_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.mmap = simple_mmap,// mmap接口
.release = simple_release,
};
在初始化的时候分配内存:
buffer = kmalloc(4096,GFP_KERNEL);
printk(" mmap buffer = %p\n",buffer);
buffer_area=(int *)(((unsigned long)buffer + PAGE_SIZE -1) & PAGE_MASK);
for (virt_addr=(unsigned long)buffer_area; virt_addr<(unsigned long)buffer_area+4096;
virt_addr+=PAGE_SIZE)
{
SetPageReserved(virt_to_page(virt_addr)); /*将页配置为保留*/
}
memset(buffer,'C',100);// 初始化为'C'
simple_mmap函数实现mmap文件接口:
static int simple_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
ret = remap_pfn_range(vma,
vma->vm_start,
virt_to_phys((void*)((unsigned long)kmalloc_area)) >> PAGE_SHIFT,
vma->vm_end-vma->vm_start,
PAGE_SHARED);
if(ret != 0) {
return -EAGAIN;
}
return 0;
}
测试程序参考代码如下:
int main(void)
{
int fd;
char *addr=NULL;
fd = open("/dev/mmap",O_RDWR);
if(fd < 0) {
perror("open");
exit(1);
}
addr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED,
fd, 0);
if(addr == MAP_FAILED) {
perror("mmap");
exit(1);
}
printf("%s\n", addr);
memset(addr,'f',100);
addr[0]='p';
printf("%s\n", addr);
munmap(addr,4096);
addr=NULL;
close(fd);
fd = open("/dev/mmap",O_RDWR);//重新打开进行验证
if(fd < 0) {
perror("open");
exit(1);
}
addr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED,
fd, 0);
if(addr == MAP_FAILED) {
perror("mmap");
exit(1);
}
printf("%s\n", addr);
munmap(addr,4096);
close(fd);
return(0);
}
运行结果如下:
[root@/home]#insmod demo.ko
mmap buffer = c3c82000
[root@/home]#mknod /dev/mmap c 224 0
[root@/home]#./read
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
pfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
pfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
————————————————
7.信号:从内核空间向进程发送信号。这个倒是经常遇到,用户程序出现重大错误,内核发送信号杀死相应进程。