linux 内核编程之字符设备驱动
来源:互联网 发布:淘宝客如何被卖家找到 编辑:程序博客网 时间:2024/04/29 11:46
【版权声明:转载请保留出处:blog.csdn.net/gentleliu。邮箱:shallnew*163.com】
第一个函数参数用于已知设备号的情况,from为要分配设备号的起始值,count是所请求的连续设备号的个数,name为设备名称。
第二个函数参数用于未知设备号的情况,dev用于保存申请成功后的第一个设备号,baseminor第一个要使用的次设备号,常常为0,另外参数同上。该函数可以自动避开设备号重复的冲突,会使用一个未使用的设备号。
由于对字符设备的访问是通过文件系统内的设备名称来访问的,设备名称位于目录/dev下.为了便于系统管理,设置了和设备名称一一对应的设备号,它分为主设备号和次设备号.通常来说,主设备号标示了设备对应的驱动程序,次设备号则用来分辨拥有同一个主设备号的的各个不同设备。
使用第一个函数register_chrdev_region()需要给出一个自定义的设备号,在内核中,设备号使用类型dev_t来保存,它包括了主设备号和次设备号。dev_t是一个32位的整数,其中的12位用来标示主设备号,其余的20位用来标示次设备号.我们可以使用两个宏来获得设备的主设备号及次设备号:
我们将MKDEV返回的设备号传递给register_chrdev_region()函数的第一个参数。
上面宏的实现如下:
该结构体里面的函数是字符设备驱动设计的主要内容,实现该设备驱动主要就是实现该结构体里面的函数,这些函数会在linux应用层使用函数open,close,read,write,lseek等时进行调用。
再来看以下该函数的实现:
然后就可以向模快添加该设备结构体,使用函数
其内核实现如下:
到此,设备创建就成功了,剩下的只需要实现设备上的操作了。该函数的调用通常发生在驱动模块加载函数中。
当你不使用设备时,可以注销设备,使用
下面给一个字符驱动编写的模板框架:
通过上面file_operations结构体可以看到该结构体内函数的原型,其中有一个很重要的结构struct file,该结构体如下:
内核空间和用户空间不能直接相互访问,要借助函数copy_to_user和copy_from_user才能实现内核空间和用户空间的内存拷贝。者两函数原型为:
编写Linux驱动时一般将文件的私有数据 private_data
指向设备结构体,在 read()、write()、ioctl()、llseek()等函数通过 private_data 访问设备结构体。
一般在open时设置private_data数据。
struct inode结构体中包含了大量有关文件的信息,该结构体中包含一个字符设备结构i_cdev。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针, 我们可以通过该结构体找到包含字符设备的私有信息
首先需要注册设备号,有两个函数可以实现该功能:
int register_chrdev_region(dev_t from, unsigned count, const char *name);int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
第一个函数参数用于已知设备号的情况,from为要分配设备号的起始值,count是所请求的连续设备号的个数,name为设备名称。
第二个函数参数用于未知设备号的情况,dev用于保存申请成功后的第一个设备号,baseminor第一个要使用的次设备号,常常为0,另外参数同上。该函数可以自动避开设备号重复的冲突,会使用一个未使用的设备号。
由于对字符设备的访问是通过文件系统内的设备名称来访问的,设备名称位于目录/dev下.为了便于系统管理,设置了和设备名称一一对应的设备号,它分为主设备号和次设备号.通常来说,主设备号标示了设备对应的驱动程序,次设备号则用来分辨拥有同一个主设备号的的各个不同设备。
使用第一个函数register_chrdev_region()需要给出一个自定义的设备号,在内核中,设备号使用类型dev_t来保存,它包括了主设备号和次设备号。dev_t是一个32位的整数,其中的12位用来标示主设备号,其余的20位用来标示次设备号.我们可以使用两个宏来获得设备的主设备号及次设备号:
MAJOR(dev_t dev_id); MINOR(dev_t dev_id);将主设备号和次设备号转换为dev_t类型,则可以使用下面的宏:
MKDEV(int major, int minor);其中,major为主设备号,minor为次设备号。
我们将MKDEV返回的设备号传递给register_chrdev_region()函数的第一个参数。
上面宏的实现如下:
#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))在分配好了设备号之后需要初始化设备结构体,使用函数
void cdev_init(struct cdev *cdev, const struct file_operations *fops);该函数带两参数,先看一下其结构体:
struct cdev { struct kobject kobj; struct module *owner; //所属模块 const struct file_operations *ops;//文件操作结构体 struct list_head list; dev_t dev; //设备号 unsigned int count;};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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, 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); 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 *, 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 (*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 **);};
该结构体里面的函数是字符设备驱动设计的主要内容,实现该设备驱动主要就是实现该结构体里面的函数,这些函数会在linux应用层使用函数open,close,read,write,lseek等时进行调用。
再来看以下该函数的实现:
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的ops。}所以在调用该函数之前,还需要初始化file_operations结构体。一般使用C标记式结构初始化语法,如下:
static const struct file_operations xxx_fops ={ .owner = THIS_MODULE, .llseek = xxx_llseek, .read = xxx_read, .write = xxx_write, .ioctl = xxx_ioctl, .open = xxx_open, .release = xxx_release,};之后再定义该设备结构体所有者:
cdev.owner = THIS_MODULE;
然后就可以向模快添加该设备结构体,使用函数
int cdev_add(struct cdev *p, dev_t dev, unsigned count);参数p为将要添加的设备结构体,dev为该设备要求的第一个设备号,count为关联到设备的设备号数目,常常为1。
其内核实现如下:
int cdev_add(struct cdev *p, dev_t dev, unsigned count){ p->dev = dev; p->count = count; return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);}
到此,设备创建就成功了,剩下的只需要实现设备上的操作了。该函数的调用通常发生在驱动模块加载函数中。
当你不使用设备时,可以注销设备,使用
void cdev_del(struct cdev *dev);还应该释放原先申请的设备号:使用
void unregister_chrdev_region(dev_t from, unsigned count);这两函数的调用通常发生在驱动模块卸载函数中。
下面给一个字符驱动编写的模板框架:
#include <linux/init.h>#include <linux/module.h>#include <linux/cdev.h>#include <linux/fs.h>#define DEV_NAME "xxx_cdev"int xxx_major = 0;dev_t xxx_devno;struct xxx_cdev_t { struct cdev cdev;} xxx_dev;int xxx_open(struct inode *inode, struct file *filep){ return 0;}static const struct file_operations xxx_fops = { .owner = THIS_MODULE, .open = xxx_open,};static int __init xxx_init(void){ int ret; if (xxx_major) { xxx_devno = MKDEV(xxx_major, 0); register_chrdev_region(xxx_devno, 1, DEV_NAME); } else { alloc_chrdev_region(&xxx_devno, 0, 1, DEV_NAME); } cdev_init(&xxx_dev.cdev, &xxx_fops); xxx_dev.cdev.owner = THIS_MODULE; if ((ret = cdev_add(&xxx_dev.cdev, xxx_devno, 1) < 0)) { unregister_chrdev_region(xxx_devno, 1); return ret; } return 0;}static void __exit xxx_exit(void){ unregister_chrdev_region(xxx_devno, 1); cdev_del(&xxx_dev.cdev);}module_init(xxx_init);module_exit(xxx_exit);MODULE_LICENSE("GPL");下面来讲一讲file_operations结构体成员函数的实现,因为这些函数是字符设备和内核接口,用户态应系统调用的最终实施者,实现字符设备驱动主要就是实现这些函数。一般read,write,ioctl这些函数一般都会实现。
通过上面file_operations结构体可以看到该结构体内函数的原型,其中有一个很重要的结构struct file,该结构体如下:
struct file { /* * fu_list becomes invalid after file_free is called and queued via * fu_rcuhead for RCU freeing */ union { struct list_head fu_list; struct rcu_head fu_rcuhead; } f_u; struct path f_path;#define f_dentry f_path.dentry#define f_vfsmnt f_path.mnt const struct file_operations *f_op; spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */ atomic_long_t f_count; unsigned int f_flags; fmode_t f_mode; 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;#endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping;#ifdef CONFIG_DEBUG_WRITECOUNT unsigned long f_mnt_write_state;#endif};其中private_data 是一个有用的资源, 在系统调用间保留状态信息,一般在open中设置它。
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);读写函数第一个参数为文件结构体指针,第二个参数是用户空间的内存地址,在内核空间不能直接读写,第三个参数是要读写的文件字节数,第四个参数是读写位置相对于文件开头的位置。
内核空间和用户空间不能直接相互访问,要借助函数copy_to_user和copy_from_user才能实现内核空间和用户空间的内存拷贝。者两函数原型为:
unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count);unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count);函数返回不能复制的字节数,完全复制返回0。
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);第三个参数为命令,第四个参数为要传入的参数。
编写Linux驱动时一般将文件的私有数据 private_data
指向设备结构体,在 read()、write()、ioctl()、llseek()等函数通过 private_data 访问设备结构体。
一般在open时设置private_data数据。
struct inode结构体中包含了大量有关文件的信息,该结构体中包含一个字符设备结构i_cdev。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针, 我们可以通过该结构体找到包含字符设备的私有信息
2 0
- linux 内核编程之字符设备驱动
- Linux内核模块编程-字符设备驱动
- Linux内核编程七:字符设备驱动
- Linux内核之字符设备驱动
- Linux 内核模块编程的第一个字符设备驱动
- Linux内核开发之简单字符设备驱动(上)
- linux高级字符设备驱动之 二 内核等待队列
- Linux内核开发之简单字符设备驱动(上)
- Linux内核开发之简单字符设备驱动(下)
- linux内核模块开发之字符设备驱动
- Linux内核开发之简单字符设备驱动(上)
- Linux内核开发之简单字符设备驱动(下)
- linux内核字符设备驱动之读操作
- linux内核字符设备驱动之写操作
- Linux设备驱动之《字符设备驱动》
- Linux设备驱动之字符设备驱动
- Linux设备驱动之字符设备驱动
- Linux设备驱动之字符设备驱动
- 浅谈安卓的不完全退出问题
- 代码库 显示多项式
- TLS 编程一
- Diffie-Hellman
- 首字母变大写
- linux 内核编程之字符设备驱动
- javascript内部原理篇[__proto__和prototype]
- gitweb
- 2013年Linux领域重要事件回顾
- Rational Rose 使用技巧
- 窗体始终在最前面
- 神级天赋市房管局首付技术犯规是发给
- 浅谈Android的Rotation动画的应用
- Linux领域重要事件回顾