设备驱动的艺术之旅 - 无处不在的字符设备<一>

来源:互联网 发布:adobe br是什么软件 编辑:程序博客网 时间:2024/05/02 02:31

From: 设备驱动的艺术之旅

如果再见不能红着眼,是否还能红著脸 - CCNN

楔子

那一年我一直以为LDD是这世间最为复杂的书籍之一了,那一年网络上总是充斥着LDD学习XX等字面的Blog,但是总是浮于表面,沉不到底。那一年我还没有毕业,但却是一个满怀理想的小愤青。那一年整整一年都是在学校的实验室 + 机房度过的,但是依然阻挡不了我的那种学习的渴望!那一年,那一年… 我的那一年!

一、2年前的回忆录

哇哇..最近看LDD3看的如痴如醉啊,刚进入这一章,我就迫不及待的把源码下了下来,然后编译想先看看结果。最起码对Makefile文件和编译可以说有了完全的认识。照理Makefile中内核路劲换了,就可以了可是一编译就出错了。百思不其解。源码不至于会有问题。于是仔细看错误,一百度才发现是这么回事。先记下。
我的内核版本是2.6.24.
编译后。

scripts/Makefile.build:46: *** CFLAGS was changed in "/home/study/3/scull/Makefile". Fix it to use EXTRA_CFLAGS。 停止。

这是提示。好吧他说fix it to use ..那好。我换。把CFLAGS换为EXTRA_CFLAGS在make
有提示:

/home/study/3/scull/main.c:17:26: linux/config.h: No such file or director

提示文件不存在。好,我把你删掉,在编译。有有提示

/home/study/3/scull/access.c:106: error: dereferencing pointer to incomple。

错误很多,不过都在access.c这个文件里,
貌似是定义错误。解决方法,在access.c中加入#include

二、Scull 字符设备 - simple character utility for loading localities

1、Scull设计

编写驱动程序第一步就是定义驱动程序为用户提供的能力(机制)。
Scull源码实现了下列设备。
Scull0~scull3
全局内存区域组成。
Scullpipe0~scullpipe3
这四个FIFO设备与管道类似。一个进程读取由一个进程写入的数据。

2、主设备号和次设备号

什么是设备号?不知道你们有没有玩过单片机。如果玩过对这货就一定很熟悉。这货也就是
硬件需要和识别的一个编号。相当于人的名字一样。
/dev 这个文件夹简单称之为文件系统树节点。字符设备驱动可以通过ls –l | grep “rw”输出第一列中的c来识别。
不难看出既有”c”开头,也有”b”开头。还有”d”,”l”等等。
都是各种硬件设备的信息。
主设备号和次设备号就是所属用户之后的数字便是设备号。
主设备号标识设备对应的驱动程序。而此设备号是内核用的。
一般而言一个主设备号对应一个驱动程序。当然现在也有一对多了。

3、设备编号的内部表达

一般在内核中,使用dev_t类型(头文件为#include

4、分配和使释放设备编号

想要创建一个字符设备之前,应该先获得编号。怎么获取?使用。
register_chrdev_region(需要头文件#include

5、动态分配主设备号

上述已经讲明了分配和释放,这里为什么还要讲。我的理解是这叫循序渐进。
一般主设备号都已经静态分配过了可在源码树中documenation中的devices.txt中显示这些清单。
太多了。使用more看 看这一部分。可很清晰的看到这是软盘。
正是因为太多。所以每个都去静态分配很容易冲突,从而导致错误。所以应该在某一个设备号能用的时候去动态随机分配,总而言之,应该是是alloc_chrdev_region而不是register_chrdev_region
但是也有缺点:因为不能保持一致,所以无法预先创建设备节点。一旦分配了设备号。就可以从/proc/devices中读取。
典型的/proc/devices如下。
Character devices //字符设备

1 mem2 pty  //终端吧  console3 ttyp  //tty设备4 ttys6 lp7 vcs10 misc13 input14 sound  21 sg180 usb Block devices: 块设备2 fd8 sd11 sr   65 sd66 sd

像上面的软盘应该属于块设设备。
fd表示软盘。
当然里面有些我也不认识。譬如sd就不知道是啥?难道是sd移动硬盘?sg也不知道是啥?难道是搜噶? 哈哈。常用的记下。剩下的慢慢来把
下面的一个脚本文件来诠释下载入字符驱动
#!/bin/sh
# Id: scull_load,hewen 2012/8/9 17:30 ok $
module=”scull”
device=”scull”
mode=”664”

# Group: since distributions do it differently, look for wheel or use staffif grep -q '^staff:' /etc/group; then    group="staff"else    group="wheel"fi# invoke insmod with all arguments we got# and use a pathname, as insmod doesn't look in . by default/sbin/insmod ./$module.ko $* || exit 1# retrieve major numbermajor=$(awk "\$2==\"$module\" {print \$1}" /proc/devices)# Remove stale nodes and replace them, then give gid and perms# Usually the script is shorter, it's scull that has several devices in it.rm -f /dev/${device}[0-3]mknod /dev/${device}0 c $major 0mknod /dev/${device}1 c $major 1mknod /dev/${device}2 c $major 2mknod /dev/${device}3 c $major 3ln -sf ${device}0 /dev/${device}chgrp $group /dev/${device}[0-3]chmod $mode  /dev/${device}[0-3]rm -f /dev/${device}pipe[0-3]mknod /dev/${device}pipe0 c $major 4mknod /dev/${device}pipe1 c $major 5mknod /dev/${device}pipe2 c $major 6mknod /dev/${device}pipe3 c $major 7ln -sf ${device}pipe0 /dev/${device}pipechgrp $group /dev/${device}pipe[0-3]chmod $mode  /dev/${device}pipe[0-3]rm -f /dev/${device}singlemknod /dev/${device}single  c $major 8chgrp $group /dev/${device}singlechmod $mode  /dev/${device}singlerm -f /dev/${device}uidmknod /dev/${device}uid   c $major 9chgrp $group /dev/${device}uidchmod $mode  /dev/${device}uidrm -f /dev/${device}wuidmknod /dev/${device}wuid  c $major 10chgrp $group /dev/${device}wuidchmod $mode  /dev/${device}wuidrm -f /dev/${device}privmknod /dev/${device}priv  c $major 11chgrp $group /dev/${device}privchmod $mode  /dev/${device}priv

上述是一个完整的调用脚本。
再看一个缩略版的脚本。
#!/bin/sh
# Id: scull_load,hewen 2012/8/9 17:30 ok $
module=”scull”
device=”scull”
mode=”664”

/sbin/insmod ./$module.ko $* || exit 1rm -f /dev/${device}[0-3]major=$(awk "\$2==\"$module\" {print \$1}" /proc/devices)mknod /dev/${device}0 c $major 0mknod /dev/${device}1 c $major 1mknod /dev/${device}2 c $major 2mknod /dev/${device}3 c $major 3  group="staff" grep -q '^staff:' /etc/group || group="wheel"chgrp $group /dev/${device}pipe[0-3]chmod $mode  /dev/${device}pipe[0-3]

不难看出前者比后者多了几个索引节点罢了。
其实要创建一个脚本去载入驱动,只需要添加和调整mknod语句即可。
完整的源码应该是12个节点。包括Scull0~scull3,Scullpipe0~scullpipe3
,scullsingle, scullpriv, sculluid, scullwuid.12个节点设备。
相反当然有节点装载也就是卸载。
如果去脚本装载和卸载字符驱动显得有点繁琐。那么说怎么解决呢。加一个参数传递给insmod 到时候装驱动就像模块一样。insmod rmmod。见上述参数。
那还是上面的问题、怎么去获取设备号。怎么连接?
int scull_init_module(void)
{
int result, i;
dev_t dev = 0;

/* * Get a range of minor numbers to work with, asking for a dynamic * major unless directed otherwise at load time. */       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;       }        /*        * allocate the devices -- we can't have them static, as the number        * can be specified at load time        */       scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);       if (!scull_devices) {              result = -ENOMEM;              goto fail;  /* Make this more graceful */       }       memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));        /* Initialize each device. */       for (i = 0; i < scull_nr_devs; i++) {              scull_devices[i].quantum = scull_quantum;              scull_devices[i].qset = scull_qset;              init_MUTEX(&scull_devices[i].sem);              scull_setup_cdev(&scull_devices[i], i);       }        /* At this point call the init function for any friend device */       dev = MKDEV(scull_major, scull_minor + scull_nr_devs);       dev += scull_p_init(dev);       dev += scull_access_init(dev);#ifdef SCULL_DEBUG /* only when debugging */       scull_create_proc();#endif       return 0; /* succeed */  fail:       scull_cleanup_module();       return result;}

这里把获取设备号的的代码放在了初始化函数中,由上一节的学习我知道。初始化函数也就是注册模块设施。所在初始化的时候获取设备号显得多么和谐。哈哈。
具体获取其实只有一点代码。

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;  //返回初始值       }}

Ok了 。这样四的问题就可以解决了。

6、字符设备中的数据结构

昨天理解了驱动程序是如何工作的。每个驱动程序都会匹配一个设备号,从而知道是要干嘛的。而获取设备号的过程也很简单、就是先创建设备。获得设备号。然后动态分配。
但是设备编号只是最初的一步,而后面还有驱动组件。大部分驱动程序操作都有三个重要的数据结构。
这些数据结果所起到的作用我认为就是操作之前需要准备的工作。
这三个数据结构分别是file_operations、file、inode, 文件操作、文件结构、索引节点。

6.1、file_operations

想起昨天的问题、驱动程序现在已经有了标识符也就是标记设备号,那么到底如如何把驱动程序和设备号连接? 就是匹配?
其实就是用file_operations这个数据结构来进行连接的(头文件#include

6.2、File 结构

所需头文件(#include <linux/fs.h>)
定义struct file数据结构,这与用户空间程序中的file没有半毛钱关系。
Struct file是一个内核结构。不会出现在用户空间程序中。
在看file 结构清单
mode_t f_mode;
文件模式,通过FMODE_READ或者FMODE_WRITE来标识文件是什么属性。(权限)
Loff_t f_pos;
当前读写的位置。
Unsigned int f_flags;
文件标志。
Struct file_operions *f_op;
与文件相关的操作。
Void *private_data;
Open调用驱动程序的方法。
Struct dentry *f_dentry;
文件对应的目录项。

6.3、inode 结构 (索引节点)

Dev_t i_rdev;
对表示设备文件的inode结构,该字段包含真正的设备编号。
Struct cdev *icdev
表示字符设备的内部结构。
而i_rdev在2.5内核发生了变化,所以加了两个宏,用来从一个inode中获取设备号(主和次)。
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
直接使用宏代替icdev即可。
三个重要数据结构就完了

7、字符设备的注册

如果说自己想自己写一个独立字符内部结构,那么怎么去写呢、?
Struct cdev *my_cdev=cdev_alloc(); //动态分配
My_cedv_>ops=&my_fops;//指向自己的内部结构

Void cdev_init(struct *cdev,struct file_operations *fops); //初始化设置cdev结构
Int cdev_add(struct cdev *cdev,dev_t num,unsigned int count); //这函数不陌生是调用添加设备
Void cdev_del(struct cdev *dev); //移除

7.1、scull中的设备注册

Scull中是通过struct scull_dev:表示每个设备。

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 cdev这个结构必须初始化,并添加到系统中,scull完成这个任务的代码是

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

因为cdev已经被嵌入到struct scull_dev中了。因此必须调用cdev_init来执行结构初始化。

Ps.看的出来两年前我的文笔也很是不错啊(照着书抄的)。

By: Keven - 点滴积累

0 0
原创粉丝点击