怎么编写驱动程序
1.确定主设备号。
2.定义自己的file_operations结构体。
3.实现对应的open/read/write等函数,填入file_operations结构体。
4.把file_operations结构体告诉内核:注册驱动程序。
5.谁来注册驱动函数=呢?得有一个人口函数:安装驱动程序时,就会去调用这个出口函数。
6有人口函数就应该有出口函数:卸载驱动程序是,就会去调用这个出口函数。
7.其他完善:提供设备信息,自动创建设备节点。
app调用的系统调用函数open,read,write等系统调用函数时,会先调用sys_open,sys_read,sys_write,再根据设备判断是普通文件,还是设备文件,进而调用drv_open,drv_read,drv_write等驱动程序。那么驱动程序怎么提前写出来呢?也就是前面的七点
struct file { union { struct llist_node fu_llist; struct rcu_head fu_rcuhead; } f_u; struct path f_path; struct inode *f_inode; /* cached value */ const struct file_operations *f_op; /* * Protects f_ep_links, f_flags. * Must not be taken from IRQ context. */ spinlock_t f_lock; atomic_long_t f_count; unsigned int f_flags; fmode_t f_mode; struct mutex f_pos_lock; loff_t f_pos; struct fown_struct f_owner; const struct cred *f_cred; struct file_ra_state f_ra; u64 f_version; #ifdef CONFIG_SECURITY void *f_security; #endif /* needed for tty driver, and maybe others */ void *private_data; #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct list_head f_ep_links; struct list_head f_tfile_llink; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
struct file 这个结构体也就是我们使用open函数的返回值,是一个整数,可以暂时当做设备描述符。const struct file_operations *f_op;结构体指针,指向file_operations结构体,这个结构体里面就有驱动程序
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 (*iterate) (struct file *, struct dir_context *); int (*iterate_shared) (struct file *, struct dir_context *); unsigned int (*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 *); 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); int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t, u64); ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *, u64); };
将open,write,read等函数填入到file_operations结构体中。而file_operations由register_chrdev(主设备号,file_operations)将设备注册到chrdev【】数组中。注册之后就可以通过之前系统分配的设备号或者自己指定的设备号就可以调用这个设备的驱动函数了。
开始编写驱动程序
参照liunx/char/misc.c进行编写
首先添加头文件
/* 总体流程 1.确定主设备号。 2.定义自己的file_operations结构体。 3.实现对应的open/read/write等函数,填入file_operations结构体。 4.把file_operations结构体告诉内核:注册驱动程序。 5.谁来注册驱动函数=呢?得有一个人口函数:安装驱动程序时,就会去调用这个出口函数。 6有人口函数就应该有出口函数:卸载驱动程序是,就会去调用这个出口函数。 7.其他完善:提供设备信息,自动创建设备节点。 */ #include <linux/module.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/miscdevice.h> #include <linux/kernel.h> #include <linux/major.h> #include <linux/mutex.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/stat.h> #include <linux/init.h> #include <linux/device.h> #include <linux/tty.h> #include <linux/kmod.h> #include <linux/gfp.h> /* 1.确定主设备号。*/ static int major = 0; static char kernel_buf[1024]; static struct class *hello_class; #define MIN(a,b)(a<b?a:b) /*3.实现对应的open/read/write等函数,填入file_operations结构体。*/ static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { int err; printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__); err = copy_to_user(buf, kernel_buf, MIN(1024,size)); return MIN(1024, size); } static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset) { int err; printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__); err = copy_from_user(kernel_buf, buf, MIN(1024,size)); return MIN(1024,size); } static int hello_drv_open (struct inode *node, struct file *file) { printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__); return 0; } static int hello_drv_release (struct inode *node, struct file *file) { printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__); return 0; } /* 2.定义自己的file_operations结构体。*/ static const struct file_operations hello_drv = { .owner = THIS_MODULE, .open = hello_drv_open, .read = hello_drv_read, .write = hello_drv_write, .release = hello_drv_release, }; /*4.把file_operations结构体告诉内核:注册驱动程序。*/ /*5.谁来注册驱动函数=呢?得有一个人口函数:安装驱动程序时,就会去调用这个出口函数。*/ static int __init hello_init(void) { int err; printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__); register_chrdev(0, "hello", &hello_drv);//应用程序访问某一个驱动程序需要根据某一个设备节点才能访问,那么如何才能设置设备节点那?? hello_class = class_create(THIS_MODULE, "hello"); err = PTR_ERR(hello_class); if (IS_ERR(hello_class)) { printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__); unregister_chrdev(major, "hello"); return -1; } device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); return 0; } /*6有人口函数就应该有出口函数:卸载驱动程序是,就会去调用这个出口函数。*/ static void __exit hello_exit(void) { printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__); device_destroy(hello_class,MKDEV(major, 0)); class_destroy(hello_class); unregister_chrdev(major, "hello"); } /*7.其他完善:提供设备信息,自动创建设备节点。*/ module_init(hello_init);//将hello_init修饰为入口函数 module_exit(hello_exit);//将hello_exit修饰为出口函数 MODULE_LICENSE("GPL");//表示同意加入GPL协议,开源协议
注意在liunx上编译后放在6ull开发板上运行时会出现
ls: cannot access ‘/dev/hello’: No such file or directory
但是hello这个驱动程序又已经加载进了内核中。这里只是没有dev设备节点,所以这里手动添加设备节点。
mknod /dev/hello c 240 0
添加之后就可以进行测试程序的操作了。