Linux应用层与内核驱动层3种交互方式

本文主要是总结出应用层与内核驱动层的主要交互方式,并提供示例代码分析交互过程。但不涉及更细节的内核代码的分析。
应用层与内核驱动层交互的方式多种多样,这里只写出了我目前理解到的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页对齐的时候,由于不够一个页内存空间。因为是页对齐,所以系统会默认申请一个页内存空间,超过一个则除以页获得需要的页的个数。

    原文作者:清风不识妻美
    原文地址: https://blog.csdn.net/zj646268653/article/details/112212131
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞