内核用dev_t类型( )来保存设备编号,在V2.6中,dev_t是一个32位的数,12位表示主设备号,20位表示次设备号。
在实际使用中,是通过 中定义的宏来转换格式。
(dev_t)-->主、次设备号 | MAJOR(dev_t dev) MINOR(dev_t dev) |
主、次设备号-->(dev_t) | MKDEV(int major,int minor) |
建立一个字符设备之前,驱动程序首先要做的事情就是获得设备编号。
#include //指定设备编号 int register_chrdev_region(dev_t first, unsigned int count,char *name); 成功时返回0,错误是返回错误码。 first的次设备好常被置为0,但不是必需的。count是所请求的连续设备编号的个数,name是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中 //动态生成设备编号 dev仅用于输出参数,调用成功后将保存已分配范围的第一个编号, firstminor被请求的第一个次设备号,通常是0.count,name同register_chrdev_region。 void unregister_chrdev_region(dev_t first, unsigned int count); //释放设备编号 在模块清除函数中调用unregister_chrdev_region函数。 |
分配主设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。
- if (scull_major) {
- dev = MKDEV(scull_major, scull_minor);
- result = register_chrdev_region(dev, scull_nr_devs, "scull");
- } else {
- result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,"scull");
- scull_major = MAJOR(dev);
- }
- if (result < 0) {
- printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
- return result;
- }
- struct file_operations {
- struct module *owner;
- 指向拥有该模块的指针,一般初始化为THIS_MODULE,它是定义在<linux/module.h>中的一个宏。
- loff_t (*llseek) (struct file *, loff_t, int);
- llseek用来修改文件的当前读写位置,并将新位置作为返回值。loff_t是一个长偏移量,至少占64位宽。如果这个函数指针为NULL,对seek的调用将以某种不可预期的方式修改file结构中的计数器
- ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
- 从设备中读取数据,该函数赋为NULL时,将导致read系统调用出错并返回-EINVAL(非法参数)
- ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
- 向设备中写数据,该函数赋为NULL时,将导致write系统调用出错并返回-EINVAL(非法参数)
- 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 *);
- poll方法是poll,epoll和select这三个系统调用的后端实现。用来查询某个或多个文件描述符上的读取或写入是否会被阻塞。poll应该返回一个位掩码,用来指出非阻塞的读取或写入是否可能,并且也会向内核提供将调用进程置于休眠状态知道I/O变为可能时的信息,如果设为NULL,则设备会被认为可读可写,且不会被阻塞。
- int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
- ioctl提供了一种执行设备特定命令的方法,另外内核还能识别一部分ioctl命令,而不必调用fops表中的ioctl。若为NULL,ioctl系统调用将返回错误(-ENOTTY,“No such ioctl for device”)
- 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 *);
- mmap用于请求将设备内存映射到进程地址空间。若为NULL,mmap系统调用将返回-ENODEV.
- int (*open) (struct inode *, struct file *);
- 若为NULL,则设备的打开操作永远成功,但系统不会通知驱动程序。
- int (*flush) (struct file *, fl_owner_t id);
- 关闭设备文件描述符副本时,执行设备上尚未完结的操作,(仅用于几个驱动程序),若为NULL,忽略用户请求。
- int (*release) (struct inode *, struct file *);
- 当file结构被释放时,调用这个操作,与open相仿,也可置为NULL.
- 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 **);
- };
- struct file_operations scull_fops = {
- .owner = THIS_MODULE,
- .llseek = scull_llseek,
- .read = scull_read,
- .write = scull_write,
- .ioctl = scull_ioctl,
- .open = scull_open,
- .release = scull_release,
- };
- 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;
- //文件的关联操作可以在任何需要的时候修改,这种技巧允许相同的open代码可以根据要打开的次设备号替换filp->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;
- //可用于任何目的或者忽略这个字段。驱动程序可以用这个字段指向已分配的数据,但一定要在内核销毁file结构之前在release方法中释放内存。
- #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
- };
- unsigned int iminor(struct inode * inode);
- unsigned int imajor(struct inode * inode);
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
②初始化struct cdev
void cdev_init(struct cdev *cdev, const struct file_operations *fops)③初始化cdev.owner
cdev.owner = THIS_MODULE;
④cdev设置完成,通知内核struct cdev的信息(在驱动程序还没完全准备好处理设备上的操作时,就不能调用cdev_add)
int cdev_add(struct cdev *cdev, dev_t num, unsigned count)
⑤从系统中移除一个字符设备:
void cdev_del(struct cdev *dev)
将cdev结构传递到cdev_del函数之后,就不应再访问cdev结构了。
5.scull的内存模型
scull的结构(可以用来表示每个设备)如下:
- struct scull_dev{
- struct scull_qset * data; //指向第一个量子集的指针
- int quantum; //量子的大小(每个指针分配的内存)
- int qset; //当前量子集的大小(指针数组)
- unsigned long size; //保存在其中的数据总量
- unsigned int access_key; //由sculluid和scullpriv使用
- struct semaphore sem; //互斥信号量
- struct cdev cdev; //字符设备结构
- };
实际的数据结构:
- struct scull_qset{
- void **data;
- struct scull_qset *next;
- }
- static void scull_setup_cdev(struct scull_dev * dev,int index)
- {
- int err,devno = MKDEV(scull_major,scull_minor+index);
- cdev_init(&dev->cdev,&scull_fops) ;
- dev->cdev.owner = THIS_MODULE;
- dev->cdev.ops = &scull_fops;
-
- err = cdev_add(&dev->cdev,devno,1);
- /*fail gracefully if need be*/
- if (err)
- printk(KERN_NOTICE"Error %d adding scull%d",err,index);
- }
- #include <linux/kernel.h>
- container_of(pointer,container_type,container_field);
- int scull_open(struct inode *inode,struct file *filp)
- {
- struct scull_dev *dev;
- dev = container_of(inode->i_cdev,struct scull_dev,cdev);
- filp->private_data = dev;
- if ((filp->f_flags & O_ACCMODE) == O_WRONLY){
- scull_trim(dev);//当以写方式打开时,把设备长度截为0
- }
- return 0;
- }
- int scull_trim(struct scull_dev *dev)
- {
- struct scull_qset *next,*dptr;
- int qset = dev->qset;
- int i;
- for (dptr = dev->data;dptr;dptr = next){
- if (dptr->data){
- for(i = 0; i < qset; i++)
- kfree(dptr->data[i]);
- kfree(dptr->data);
- dptr->data = NULL;
- }
- next = dptr->next;
- kfree(dptr);
- }
- dev->size = 0;
- dev->quantum = scull_quantum;
- dev->qset = scull_qset;
- dev->data = NULL;
- return 0;
- }
- /*Follow the list*/
- struct scull_qset *scull_follow(struct scull_dev *dev, int n)
- {
- struct scull_qset *qs = dev->data;
- /* Allocate first qset explicitly if need be */
- if (! qs) {
- qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
- if (qs == NULL)
- return NULL;
- memset(qs, 0, sizeof(struct scull_qset));
- }
- /* Then follow the list */
- while (n--) {
- if (!qs->next) {
- qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
- if (qs->next == NULL)
- return NULL;
- memset(qs->next, 0, sizeof(struct scull_qset));
- }
- qs = qs->next;
- continue;
- }
- return qs;
- }
- #include <linux/slab.h>
- void *kmalloc(size_t size,int flags);
- void kfree(void *ptr);
- #include <asm/uaccess.h>
- unsigned long copy_to_user(void __user*to,const void *from,unsigned long count);
- unsigned long copy_from_user(void *to,const void __user*from,unsigned long count);
- ssize_t read(struct file *filp,char __user *buff,size_t count,loff_t *offp);
- ssize_t write(struct file * filp,const char __user *buff,size_t count,loff_t *offp);

