邮箱:zhuimengcanyang@163.com 痴爱嵌入式技术的蜗牛
分类: 嵌入式
2015-06-29 13:24:59
在2.6内核中阅读驱动程序,还可以看到很多用如下方式注册字符设备驱动程序
这是老式的经典方式,但是新的代码不应该使用这种接口,因为这种机制在未来的内核中会消失。
- /**
- * register_chrdev() - Register a major number for character devices.
- * @major: major device number or 0 for dynamic allocation
- * @name: name of this range of devices
- * @fops: file operations associated with this devices
- *
- * If @major == 0 this functions will dynamically allocate a major and return
- * its number.
- *
- * If @major > 0 this function will attempt to reserve a device with the given
- * major number and will return zero on success.
- *
- * Returns a -ve errno on failure.
- *
- * The name of this device has nothing to do with the name of the device in
- * /dev. It only helps to keep track of the different owners of devices. If
- * your module name has only one type of devices it's ok to use e.g. the name
- * of the module here.
- *
- * This function registers a range of 256 minor numbers. The first minor number
- * is 0.
- */
- int register_chrdev(unsigned int major, const char *name, // name 是驱动程序的名字,出现在 /proc/devices 中
- const struct file_operations *fops)
其对应的卸载驱动程序是:
- int unregister_chrdev(unsigned int major, const char *name)
open方法
open方法提供给驱动程序以初始化的能力,为以后的操作完成初始化做准备。
原型如下:
- int (*open) (struct inode *, struct file *);
scull_open
利用结构体container_of,获取scull_dev.
- int scull_open(struct inode *inode, struct file *filp)
- {
- struct scull_dev *dev; /* device information */
- dev = container_of(inode->i_cdev, struct scull_dev, cdev);
- filp->private_data = dev; /* for other methods */ // 保存起来
- /* now trim to 0 the length of the device if open was write-only */
- if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
- if (down_interruptible(&dev->sem))
- return -ERESTARTSYS;
- scull_trim(dev); /* ignore errors */
- up(&dev->sem);
- }
- return 0; /* success */
- }
container_of(pointer, container_type, container_field);
pointer 为指向container_field字段的指针;而container_field字段包含在container_type中;
函数返回的是 指向container_type 类型的指针。
release方法
release方法,主要实现open方法相反的任务:
1. 释放由open分配的,保存在filp->private_data中的所有内容;
2. 在最后一次关闭操作时,关闭设备。
注意:并不是每个close系统调用都会引起对release方法的调用。只有那些真正释放设备数据结构的close调用才会调用这个方法。内核对每个file结构维护其被使用多少次的计数器。无论是fork还是dup,都不会创建新的数据结构(仅仅由open创建),它们只是增加已有结构中的计数。只有在file结构中的计数为0时,close系统调用才会执行release方法,这只在删除这个结构时才发生。
release方法与close系统调用间的关系保证了对于每次open驱动程序只会看到对应的一次release调用。
read 和 write
函数原型:
读和写是针对用户空间来说的。
- 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);
filp: 是文件指针;
count: 是请求传输的数据长度;
buff:是指向用户空间的缓冲区;这个缓冲区或者保存要写入的数据,或者是一个存放新读入数据的空缓冲区;
offp:是一个指向“long offset type(长偏移量类型)”对象的指针;这个对象指明用户在文件中进行存取操作的位置。
注意:read,write方法的buff参数是用户空间的指针,内核代码不能直接引用其中的内容。出现这种限制的原因有如下几个:
1. 在内核模式中运行时,用户空间的指针可能是无效的。该地址可能根本无法被映射到内核空间,或者可能指向某些随机数据;
2. 即使该指针在内核空间代表相同的东西,但用户空间的内存是分页的,而在系统调用被调用时,涉及到的内存可能根本就不在RAM中。对用户空间内存的直接引用 将导致页错误,而这对内核代码来说是不允许发生的事情。其结果可能是一个“oops”,将导致调用该系统调用的进程死亡。
3. 该指针可能由用户程序提供,而该程序可能存在缺陷或者是个恶意程序。
read和write代码要做的工作就是在用户地址空间和内核地址空间之间进行整段数据的拷贝。这种能力由内核函数提供。
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);
这两个函数作用不限于在内核和用户空间之间拷贝数据,它还会:
1. 检查用户空间的指针是否有效,如果指针无效,就不会进行拷贝;
2. 如果在拷贝过程中遇到无效地址,则会仅仅复制部分数据;
在这两种情况下,返回值是还需要拷贝的内存数量值。
read方法的任务是从设备拷贝数据到用户空间(使用copy_to_user);
write方法则是从用户空间拷贝数据到设备上(使用copy_from_user);
出错时,read和write返回都返回一个负值。大于等于0的返回值告诉调用程序成功传输了多少字节。如果在正确传输部分数据之后发生了错误,则返回值必须是成功传输的字节数,但这个错误只能在下一次函数调用时才会得到报告。
尽管内核函数通过返回负值来表示错误,而且该返回值表明了错误的类型,但运行在用户空间的程序看到的始终是作为返回值的-1。为了找到错误的原因,用户空间的程序必须访问errno变量。
read方法
- 如果返回值等于传递给read系统调用的count参数,则说明所请求的字节数传输成功完成了。这是理想情况
- 如果返回值是正的,但是比count小,则说明只有部分数据成功传送。
- 如果返回值为0,则表示已经到达了文件尾部;
- 负值,则表示发生了错误,错误码在<linux/errno.h>中定义
- ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
- loff_t *f_pos)
- {
- struct scull_dev *dev = filp->private_data;
- struct scull_qset *dptr; /* the first listitem */
- int quantum = dev->quantum, qset = dev->qset;
- int itemsize = quantum * qset; /* how many bytes in the listitem */
- int item, s_pos, q_pos, rest;
- ssize_t retval = 0;
- if (down_interruptible(&dev->sem))
- return -ERESTARTSYS;
- if (*f_pos >= dev->size)
- goto out;
- if (*f_pos + count > dev->size)
- count = dev->size - *f_pos;
- /* find listitem, qset index, and offset in the quantum */
- item = (long)*f_pos / itemsize;
- rest = (long)*f_pos % itemsize;
- s_pos = rest / quantum; q_pos = rest % quantum;
- /* follow the list up to the right position (defined elsewhere) */
- dptr = scull_follow(dev, item);
- if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
- goto out; /* don't fill holes */
- /* read only up to the end of this quantum */
- if (count > quantum - q_pos)
- count = quantum - q_pos;
- if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
- retval = -EFAULT;
- goto out;
- }
- *f_pos += count;
- retval = count;
- out:
- up(&dev->sem);
- return retval;
- }
write方法
- 返回值等于count,则完成了所请求数目的字节传送
- 返回值是正,但小于count,则 只是传输了部分数据。
- 返回值为0,意味着什么也没有写入。
- 为负值,则发生了错误。错误码在<linux/errno.h>中定义
- ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
- loff_t *f_pos)
- {
- struct scull_dev *dev = filp->private_data;
- struct scull_qset *dptr;
- int quantum = dev->quantum, qset = dev->qset;
- int itemsize = quantum * qset;
- int item, s_pos, q_pos, rest;
- ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
- if (down_interruptible(&dev->sem))
- return -ERESTARTSYS;
- /* find listitem, qset index and offset in the quantum */
- item = (long)*f_pos / itemsize;
- rest = (long)*f_pos % itemsize;
- s_pos = rest / quantum; q_pos = rest % quantum;
- /* follow the list up to the right position */
- dptr = scull_follow(dev, item);
- if (dptr == NULL)
- goto out;
- if (!dptr->data) {
- dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
- if (!dptr->data)
- goto out;
- memset(dptr->data, 0, qset * sizeof(char *));
- }
- if (!dptr->data[s_pos]) {
- dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
- if (!dptr->data[s_pos])
- goto out;
- }
- /* write only up to the end of this quantum */
- if (count > quantum - q_pos)
- count = quantum - q_pos;
- if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
- retval = -EFAULT;
- goto out;
- }
- *f_pos += count;
- retval = count;
- /* update the size */
- if (dev->size < *f_pos)
- dev->size = *f_pos;
- out:
- up(&dev->sem);
- return retval;
- }