Linux设备驱动最小的一个框架。
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> static int __init local_init(void) { return 0; } static void __exit local_exit(void) { } module_init(local_init); module_exit(local_exit); MODULE_LICENSE("GPL");
我先贴出一个字符设备驱动的demo,咱们再对照demo进行分析与讲解。
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/fs.h> //#include <asm/uaccess.h> #include <linux/uaccess.h> struct cdev local_cdev; dev_t local_dev; static struct class *cls; static int major_num = 0; static int minor_num = 0; static int local_open(struct inode *inode,struct file *file) { printk(KERN_INFO"open file opertions\n"); return 0; } ssize_t local_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { //copy_to_user(ubuf,kbuf,count); //实际上因该使用这个,用于内核上的数据复制 *buf = 100; printk("local_read\n"); return 1; } static ssize_t local_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { printk("local_write\n"); char stringdemo[count]; size_t i = 0; //memcpy(stringdemo,buf,count); copy_from_user(stringdemo,buf,count); //实际上因该使用这个,用于内核上的数据复制 for ( i = 0; i < count; i++) { printk("%c",stringdemo[i]); } return 2; } struct file_operations local_ops ={ .owner = THIS_MODULE, .open = local_open, .read = local_read, .write = local_write, }; static int __init local_init(void) { printk("\n\n\n\n\n\n\n\n\n\n"); //主设备编号的范围是 0--256 #if 1 cdev_init(&local_cdev,&local_ops); // 初始化字符设备 alloc_chrdev_region(&local_dev,0,1,"local_dong"); // 动态获取设备编号 local_cdev.dev = local_dev; // 备份设备编号 major_num = MAJOR(local_dev); minor_num = MINOR(local_dev); printk("local_dev :%d major_num :%d minor_num :%d\n",local_dev,major_num,minor_num); cdev_add(&local_cdev,local_dev,1); // 向内核添加设备 printk("cdev_add\n"); #elif major_num = 0; minor_num = 0; local_cdev.dev = local_dev; // 备份设备编号 local_dev = MKDEV(major_num,minor_num); // 获取设备编号 register_chrdev_region(local_dev,1,"local_dong2"); // 静态获取设备编号 cdev_init(&local_cdev,&local_ops); cdev_add(local_cdev,local_dev,1); // 向内核添加设备 #else major_num = register_chrdev(0,"local_dong",local_ops); 老式方法一步到位 #endif cls = class_create(THIS_MODULE, "local_dong"); printk("class_create\n"); //class_device_create(cls, NULL, MKDEV(major_num, 0), NULL, "local_dong0"); //Linux2.6.27之前时用这个 device_create(cls, NULL, MKDEV(major_num, minor_num), NULL,"local_dong0"); //目前用这个 printk("device_create\n"); return 0; } static void __exit local_exit(void) { // class_device_destroy(cls, MKDEV(major_num, 0)); device_destroy(cls,MKDEV(major_num, 0)); printk("device_destroy\n"); class_destroy(cls); printk("class_destroy\n"); cdev_del(&local_cdev); printk("cdev_del\n"); unregister_chrdev_region(MKDEV(major_num, 0), 1); printk("unregister_chrdev_region\n"); } module_init(local_init); module_exit(local_exit); MODULE_LICENSE("GPL"); /** * * class_device_create(); * class_device_destroy(); * 在2.6.27中变为: * device_create() * device_destroy() */
Makefile
这个makefile我也记不住是在哪里复制粘贴的了,反正都差不多。
$(warning KERNELRELEASE = $(KERNELRELEASE)) ifeq ($(KERNELRELEASE),) #内核的源码路径, ?= 条件赋值, uname -r 得到内核的版本号 KERNELDIR ?= /lib/modules/$(shell uname -r)/build # := 立即赋值, 得到当前的绝对路径 PWD := $(shell pwd) # -C 切换工作路径, make -c modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules* .PHONY: modules clean else # 生成模块 obj-m := 2021070601.o endif
App
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(int argc,char *argv[]) { int fd = 0; int dst = 0; fd = open("/dev/local_dong0",O_RDWR); if(fd < 0){ printf("file open failed!\r\n"); printf("fd %d\n",fd); return -1; } printf("read file return is %d\n",read(fd,&dst,sizeof(int))); printf("dst value is %d\n",dst); printf("write file return is %d\n",write(fd,"helloworld",11)); close(fd); return 0 ; }
三部分代码全部都贴在这里,感兴趣的话可以跑一下。
首先我们讲一下第一部分,驱动部分。
驱动部分是本篇文章的核心,其余两个可看可不看。
字符设备驱动的注册分为三步。
void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; }
cdev_init函数首先将传入的数组初始化,将fire_operation操作数组与字符设备的结构体绑定。
设备编号dev_t是一个无符号32位整形数,前12位为主设备编号,后20位为次设备编号。
这个函数的第三个参数就是你lsmod显示驱动的名称。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) { struct char_device_struct *cd; cd = __register_chrdev_region(0, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); *dev = MKDEV(cd->major, cd->baseminor); return 0; }
这个函数执行完毕时,lsmod就会显示出来所有的字符设备驱动,这时你的字符设备编号和字符设备名称都会看到。
int cdev_add(struct cdev *p, dev_t dev, unsigned count) { int error; p->dev = dev; p->count = count; if (WARN_ON(dev == WHITEOUT_DEV)) return -EBUSY; error = kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p); if (error) return error; kobject_get(p->kobj.parent); return 0; }
执行完这几个函数时你在/dev目录还是找不到你自己写的驱动,你还需要mknod /dev/xxx c 主设备编号 次设备编号。这个时候你就能在/dev文件下找到你的驱动了,在这个文件夹下你也能使用你的驱动了。这个地方的主设备编号就是你第二步时的字符设备编号。
每次你都需要手动的mknod去添加设备这个就很麻烦,所以自动添加的方法如下。
创建class这个就是一类设备的集合,这个class下边可以挂在非常多的同类型设备驱动。class在/sys/class/下创建一个自定义名称的文件夹,这个文件夹就可以理解为class。
将你的设备驱动挂载到class下边,并将驱动显示到/dev下边。
到这里一个字符设备的创建到显示到/dev文件夹的流程就走完了。
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; } __randomize_layout;
这个结构体里边含有file_operations,dev_t设备号。
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 (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iopoll)(struct kiocb *kiocb, bool spin); int (*iterate) (struct file *, struct dir_context *); int (*iterate_shared) (struct file *, struct dir_context *); __poll_t (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); unsigned long mmap_supported_flags; int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); 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); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int); loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t len, unsigned int remap_flags); int (*fadvise)(struct file *, loff_t, loff_t, int); } __randomize_layout;
这个里边含有read,write,open,close等操作,使用时
#define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
MAJOR(dev)中的dev就是无符号32位整数值的设备号,MAJOR(dev)结果是主设备标号,获取dev前12位的值。
MINOR(dev)结果是次设备标号,获取dev后20位的值。
MKDEV(ma,mi)中ma是主设备号,mi是次设备号,MKDEV(ma,mi)得的结果是设备编号。
执行驱动文件的makefile,生成xxx.ko文件,在执行insmod xxx.ko,安装驱动模块。
执行lsmod,查看你的驱动模块名称
执行rmmod xxx,卸载对应的驱动模块
insmod 安装驱动 rmmod 卸载驱动
lsmod 显示所有的模块 modinfo 驱动信息