本文主要是总结出应用层与内核驱动层的主要交互方式,并提供示例代码分析交互过程。但不涉及更细节的内核代码的分析。
应用层与内核驱动层交互的方式多种多样,这里只写出了我目前理解到的3种方式,至于其它等以后再做整理。
应用与驱动3种方式
所谓的应用与驱动层的交互主要是数据的传递,这里主要是使用内核提供给应用层的API接口,从而实现应用与驱动数据传递。应用层操作硬件设备也是通过内核层提供的API接口或文件设备提供的操作接口来实现调用驱动来操作硬件设备。
总结来是应用层与驱动层数据相互传递,或应用层通过驱动提供的方法来操作硬件从而来实现对硬件的操作。
- 通过系统调用open(), read(), write()等方式实现对驱动层的交互,获取驱动层数据或调用方法操作硬件。
- 通过IOCTL CMD命令方式与内核驱动层交互,从而获取驱动层数据或调用驱动层方法来操作硬件。
- 通过mmap方式来实现应用层与内核共享内存来传递数据。这种方式优点是速度快,且可以数据大块的数据量。
不管理在应用层,还是在系统NFS层,或是在内核驱动层。他们之所以能访问到同驱动/设备提供的数据或方法。主是应该LINUX内核的设备文件描述符的作用。通过为每一个设备分配固定的主,次设备号从而绑定设备与驱动。并在驱动中把操作的接口暴露给系统或应该。这样不管是在应用打开的file类型指针,或是系统层打开的inode节点信息,或驱动中的设备类型中都包含某一个设备共同信息(主、次设备号, file operation操作方法)这些都是与设备绑定好的。无论在哪个层都是相同。所以在各层能通过不同的方式访问到相同的驱动或说操作同一设备。
系统调用的方式,通过设备文件描述符找到设备主次设备号,从而找到设备并调用设备的驱动方法及传递数据。
查看/dev目录下的某个设备文件获取的主、次设备号,由驱动向内核申请或定义固定数值
crw------- 1 root root 238, 0 1月 5 09:03 /dev/demo_dev
第二、三种方式主是通过驱动提供的file operation结构体实现,结构体具体项如下
struct file_operations {
struct module *owner;
loff_t(*llseek) (struct file *, loff_t, int);
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int,
unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t(*readv) (struct file *, const struct iovec *, unsigned long,
loff_t *);
ssize_t(*writev) (struct file *, const struct iovec *, unsigned long,
loff_t *);
ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t,
void __user *);
ssize_t(*sendpage) (struct file *, struct page *, int, size_t,
loff_t *, int);
unsigned long (*get_unmapped_area) (struct file *, unsigned long,
unsigned long, unsigned long,
unsigned long);
};
以后是第二种、第三种交互方式的接口
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
int (*mmap) (struct file *, struct vm_area_struct *);
直接上代码,这里IOCTL传递的数据是结构体
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/ioctl.h>
#include <linux/types.h>
#include <linux/stat.h>
#include <linux/fcntl.h>
#include <linux/unistd.h>
#include <asm/ioctl.h>
#include <linux/ioctl.h>
#include <linux/mman.h>
#include <linux/fb.h>
#include <linux/mm.h>
typedef struct mdata{
int dev_major;
short data;
} data_t;
#define DEMO_NAME "demo_dev"
//定义命令及传递的数据
#define DEMO_OPEN _IOW('d', 1, struct mdata)
#define DEMO_CLOSE _IOW('d', 2, struct mdata)
#define DEMO_MMAP _IOW('d', 3, struct mdata)
struct demo_dev{
int major;
int minor;
int data;
void* private;
char* name;
struct device* dev;
struct class* cls;
};
static void* demo_mem;
struct demo_dev* ddev;
static int demo_open(struct inode* inode, struct file* file)
{
printk("<-KERNEL->-----Demo open-----");
demo_mem = kzalloc(PAGE_SIZE, GFP_KERNEL);
if(!demo_mem)
return -ENOMEM;
return 0;
}
static ssize_t demo_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
{
struct mdata md;
copy_from_user(&md, buf, sizeof(struct mdata));
printk("<-KERNEL->----From user major:%d, data:%d-----", md.dev_major, md.data);
return 0;
}
static ssize_t demo_read(struct file* file, char __user* buf, size_t count, loff_t* ppos)
{
struct mdata md = {
.dev_major = 120,
.data = 119,
};
copy_to_user(buf, &md, sizeof(struct mdata));
printk("<-KERNEL->-----To user major:%d, data:%d -----", md.dev_major, md.data);
return 0;
}
static long int demo_device_ioctl(struct file* file, unsigned int cmd, unsigned long int arg)
{
struct mdata mydata;
struct mdata* dd;
copy_from_user(&mydata, (struct mdata*)arg, sizeof(struct mdata));
switch(cmd){
case DEMO_OPEN:
printk("<-KERNEL-> ioctl DEMO_OPEN from user major:%d, data:%d\n", mydata.dev_major, mydata.data);
break;
case DEMO_CLOSE:
printk("<-KERNEL-> ioctl DEMO_CLOSE from user major:%d, data:%d\n", mydata.dev_major, mydata.data);
break;
case DEMO_MMAP:
dd = (struct mdata*)demo_mem;
printk("<-KERNEL-> ioctl DEMO_MMAP from user major:%d, data:%d\n", dd->dev_major, dd->data);
break;
default:
break;
}
return 0;
}
static int demo_mmap(struct file* filp, struct vm_area_struct* vma)
{
if(remap_pfn_range(vma, vma->vm_start, virt_to_phys(demo_mem) >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
printk("<----------KERNEL mmap Ok------------>");
return 0;
}
static int demo_release(struct inode* inode, struct file* file)
{
printk("-----demo release -----");
return 0;
}
static const struct file_operations ddev_fops = {
.open = demo_open,
.write = demo_write,
.read = demo_read,
.unlocked_ioctl = demo_device_ioctl,
.release = demo_release,
.mmap = demo_mmap,
};
static int __init demo_init(void)
{
printk("<-KERNEL-> Demo init ----\n");
ddev = kzalloc(sizeof(*ddev), GFP_KERNEL);
if(!ddev)
return -ENOMEM;
ddev->name = DEMO_NAME;
ddev->major = register_chrdev(0, ddev->name, &ddev_fops);
ddev->cls = class_create(THIS_MODULE, DEMO_NAME);
if(IS_ERR(&ddev->cls)){
pr_err("Demo: device class create failed\n");
goto err_class_create;
}
ddev->dev = device_create(ddev->cls, NULL, MKDEV(ddev->major, 0), NULL, DEMO_NAME);
if(IS_ERR(&ddev->dev)){
pr_err("Demo: create device failed@@\n");
goto err_device_create;
}
return 0;
err_device_create:
class_destroy(ddev->cls);
err_class_create:
unregister_chrdev(ddev->major, DEMO_NAME);
kfree(ddev);
return -EFAULT;
}
static void __exit demo_exit(void)
{
printk("<-KERNEL-> Demo exit ----!@! \n");
device_destroy(ddev->cls, MKDEV(ddev->major, 0));
class_destroy(ddev->cls);
unregister_chrdev(ddev->major, DEMO_NAME);
kfree(ddev);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_AUTHOR("Jerry<jerry@xxx.com>");
MODULE_DESCRIPTION("Driver for Demo");
MODULE_LICENSE("GPL");
应用层测试代码,这里的ioctl命令其它可以通过包含同一个头件来共用,这样就不需要在程序中重新定义。因为是自己测试为了理解命令以方便测试,这里重新在代码里自己定义了命令及数据。
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
typedef struct mdata{
int dev_major;
short data;
} data_t;
// define ioctl cmd or include corresponding driver heard file.
#define DEMO_OPEN _IOW('d', 1, struct mdata)
#define DEMO_CLOSE _IOW('d', 2, struct mdata)
#define DEMO_MMAP _IOW('d', 3, struct mdata)
struct mdata* demo_map;
int main(int argc, char** argv)
{
int fd = -1, ret = -1;
struct mdata mybuf;
struct mdata mydata = {
.dev_major = 1314,
.data = 666,
};
fd = open("/dev/demo_dev", O_RDWR);
if(fd < 0){
printf("Open demo device failed\n");
return -1;
}
puts("<----------------------- Read and Write @@@ ------------------------------>");
printf("Write to KERNEL data--> major:%d, data:%d\n", mydata.dev_major, mydata.data);
if( write(fd, &mydata, sizeof(struct mdata) < 0)){
printf("Demo wirte data error!!!\n");
return -1;
}
if( read(fd, &mybuf, sizeof(struct mdata) < 0 )){
printf("Demo read data error\n");
return -1;
}
printf("Read from KERNEL data--> major:%d, data:%d\n", mybuf.dev_major, mybuf.data);
puts("<----------------------- IOCTL DEMO_OPEN View kernel Mesges @@@ ------------------------------>");
ioctl(fd, DEMO_OPEN, &mydata);
puts("<----------------------- IOCTL MMAP @@@ ------------------------------>");
demo_map = (struct mdata*)mmap(NULL, sizeof(struct mdata), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(demo_map == NULL){
printf("mmap memory failed@@!!");
return -1;
}
demo_map->dev_major = 520;
demo_map->data = 999;
printf("MMAP Write to virtual memory data--> major:%d, data:%d\n", mydata.dev_major, mydata.data);
ioctl(fd, DEMO_MMAP, &mydata);
if(demo_map != NULL)
munmap(demo_map, sizeof(struct mdata));
close(fd);
return 0;
}
以上代码得到的执行结果
<----------------------- Read and Write @@@ ------------------------------>
Write to KERNEL data--> major:1314, data:666
# 传递给内核的数据 对应内核打印获取的数据[2] (数据正确)
Read from KERNEL data--> major:120, data:119
# 从内核打印信息[3] 在应用层获取的数据 (数据正确)
<----------------------- IOCTL DEMO_OPEN View kernel Mesges @@@ ------------------------------>
#对应内核打印的[4]信息 内核执行了ioctl DEMO_OPEN命令
<----------------------- IOCTL MMAP @@@ ------------------------------>
MMAP Write to virtual memory data--> major:1314, data:666
#应用写数据到映射的共享内存空间,在内核获取并转换成相应的数据格式并打印[6](数据正确)
dmesg | tail -10 查看内核打印信息
[1] <-KERNEL->-----Demo open-----
[2] <-KERNEL->----From user major:1314, data:666-----
[3] <-KERNEL->-----To user major:120, data:119 -----
[4] <-KERNEL-> ioctl DEMO_OPEN from user major:1314, data:666
[5] <----------KERNEL mmap Ok------------>
[6] <-KERNEL-> ioctl DEMO_MMAP from user major:520, data:999
具体的各个知识点等有时候再一一总结。比如ioctl命令的执行过程,mmap相关等内容。
其它的注意下头文件的即可,可以通过man 2 来查找相关系统函数的使用说明及包含的头文件。
注意:在mmap函数映射的内存空间,第三个参数并不是简单的内存地址,需要把地址转换成物理地址,并把内存空间转换成相应系统的页个数。如果小于PAGE_SIZE进行page页对齐的时候,由于不够一个页内存空间。因为是页对齐,所以系统会默认申请一个页内存空间,超过一个则除以页获得需要的页的个数。