linux设备驱动笔记——字符设备驱动

来源:互联网 发布:激战2阿苏拉男捏脸数据 编辑:程序博客网 时间:2024/05/16 01:14

第3章  字符设备驱动程序

 

       “全局性”是指,如果打开设备多次,所有打开它的文件描述符共享其中的数据。“持久性”是指,如果设备关闭后再次打开,数据不丢失。

       真实的驱动程序利用中断与它们的设备同步

 

主设备号和次设备号

       主设备号标识设备对应的驱动程序;次设备号由内核使用,用于正确确定设备文件所指的设备。我们可以通过次设备号获得一个指向内核设备的直接指针,也可将次设备号当作设备本地数组的索引,不管用哪种方式,除了知道次设备号用来指向驱动程序所实现的设备之外,内核本身基本上不关心关于次设备号的任何其他消息。

       设备编号的内部表达

n         在内核中,dev_t类型(在<linux/types.h>中定义)用来保存设备编号——包括主设备号和次设备号。

n         MAJOR(dev_t dev);      MINOR(dev_t dev)              MKDEV(int major, int minor)

 

       分配和释放设备编号

n         <linux/fs.h>:

int register_chrdev_region(dev_t first, unsigned int count, char *name);

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, 
                        unsigned int count, char *name);
void unregister_chrdev_region(dev_t first, unsigned int count);
 
驱动程序需要将设备编号和内部函数连接起来,这些内部函数用来实现设备的操作。

 

       动态分配主设备号

n         某些主设备号已经静态地分配给了大部分公用设备。在内核源码树的Documentation/device.txt文件中可以找到这些设备的列表。

n         一旦驱动程序被广泛使用,随机选定的主设备号可能造成冲突和麻烦

n         强烈推荐你不要随便选择一个一个当前不用的设备号做为主设备号,而使用动态分配机制获取你的主设备号。

n         动态分配的缺点是,由于分配给你的主设备号不能保证总是一样的,无法事先创建设备节点。然而这不是什么问题,这是因为一旦分配了设备号,你就可以从/proc/devices读到。为了加载一个设备驱动程序,对insmod的调用被替换为一个简单的脚本,它通过/proc/devices获得新分配的主设备号,并创建节点

#!/bin/sh
module="scull"
device="scull"
mode="664"
 
# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
 
/sbin/insmod ./$module.ko $* || exit 1
 
# remove stale nodes
 
rm -f /dev/${device}[0-3]
major=$(awk "//$2=  =/"$module/" {print //$1}" /proc/devices)
mknod /dev/${device}0 c $major 0
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3
 
# give appropriate group/permissions, and change the group.
# Not all distributions have staff, some have "wheel" instead.
 
group="staff"
grep -q '^staff:' /etc/group || group="wheel"
 
chgrp $group /dev/${device}[0-3]
chmod $mode  /dev/${device}[0-3]

n         分配主设备号的最佳方式:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。

n         Here's the code we use in scull 's source to get a major number:

 

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;
}
 
      一些重要的数据结构
n         文件操作file_operations
u       <linux/fs.h>中定义file_operations
u       __user表明指针是一个用户空间地址,因此不能被直接引用。
u       ssize_t (*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t *); 初始化设备上的异步写入操作。
u       unsigned int (*poll) (struct file *, struct poll_table_struct *);pollepollselect这三个系统调用的后端实现。可用来查询某个或多个文件描述符上的读取或写入是否会被阻塞。
u       int (*mmap) (struct file *, struct vm_area_struct *);用于请求将设备内存映射到进程地址空间。
u       int (*fsync) (struct file *, struct dentry *, int);用户调用它来刷新待处理的数据。
u       int (*fasync) (int, struct file *, int);用来通知设备其FASYNC标志发生了变化。
 
        file结构
u       struct file是一个内核结构,不会出现在用户程序中
    inode结构
u       内核用inode结构在内部表示文件,因此它和file结构不同,后者表示打开的文件描述符。对单个文件,可能会有许多个表示打开的文件描述符的file结构,但它们都指向单个inode结构。
u       dev_t i_rdev:对表示设备文件的inode结构,该字段包含了真正的设备编号。
u       struct cdev *i_cdev;struct cdev表示字符设备的内核的内部结构。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针
u       unsigned int iminor(struct inode *inode);
u       unsigned int imajor(struct inode *inode);
用来从一个inode中获得主设备号和次设备号
 
       字符设备的注册
n         内核内部使用struct cdev结构来表示字符设备。为此我们的代码应包含<linux/cdev.h>,其中定义了这个结构以及与其相关的一些辅助函数。
 

  The classic way to register a char device driver is with:

int register_chrdev(unsigned int major, const char *name,struct file_operations *fops);
int unregister_chrdev(unsigned int major, const char *name);
 
      open方法
n         在大部分驱动程序中,open完成如下工作:
u       检查设备相关错误(诸如设备未就绪或相似的硬件问题)。
u       如果是首次打开,初始化设备。
u       标别次设备号,如有必要更新f_op指针。
u       分配和填写要放在filp->private_data里的数据结构。
u       增加使用计数。
 
        container_of(pointer, container_type, container_field);

 

         struct scull_dev *dev; /* device information */
 
         dev = container_of(inode->i_cdev, struct scull_dev, cdev);
         filp->private_data = dev; /* for other methods */

      

       release方法的作用正好与open相反。这个设备方法有时也称为close。它应该:

n         使用计数减1

n         释放open分配在filp->private_data中的内存。

n         在最后一次关闭操作时关闭设备。

  The scull driver introduces two core functions used to manage memory in the Linux kernel. These functions, defined in <linux/slab.h>, are:

void *kmalloc(size_t size, int flags);

void kfree(void *ptr);

       scull中,每个设备都是一个指针链表,其中每个指针都指向一个scull_qset结构。默认情况下,每一个这样的结构通过一个中间指针数组最多可引用4000000个字节。使用了一个有1000个指针的数组,每个指针指向一个4000字节的区域。

       量子是什么??P65 每个量子占用4000个字节

       使用宏和整数值同时允许在编译期间和加载阶段进行配置,这种方法和前面选择主设备号的方法类似。对于驱动程序中任何不确定的或与策略相关的数值,我们都可以使用这种技巧。

 

       readwrite

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);从用户空间拷贝数据到设备上。

       参数buff是指向用户空间的缓冲区,这个缓冲区或者保存要写入的数据,或者是一个存放新读入数据的空缓冲区。

         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);

       这两个函数还检测用户空间的指针是否有效。

       Read方法

n         如果返回值等于最为count参数传递给read系统调用的值,所请求的字节数传输就成功完成了。这是最好的情况。

n         如果返回值是正的,但是比count小,只有部分数据成功传送。这种情况因设备的不同可能有许多原因。大部分情况下,程序会重新读数据。例如,如果你用fread函数读数据,这个库库函数会不断调用系统调用直至所请求的数据传输完成。

n         如果返回值为0,它表示已经到达了文件尾。

n         负值意味着发生了错误。值就是错误编码,错误编码在<linux/errno.h>中定义。

       read相似,根据如下返回值规则,write也可以传输少于请求的数据量:

n         如果返回值等于count,则完成了请求数目的字节传送。

n         如果返回值是正的,但小于count,只传输了部分数据。再说明一次,程序很可能会再次读取余下的部分。

n         如果值为0,什么也没写。这个结果不是错误,而且也没有什么缘由需要返回一个错误编码。再说明一次,标准库会重复调用write。以后的章节会介绍阻塞型write,我们会对这种情形最更详尽的考察。

n         负值意味发生了错误;语义与read相同。

 

原创粉丝点击