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

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 mem
2 pty  //终端吧  console
3 ttyp  //tty设备
4 ttys
6 lp
7 vcs
10 misc
13 input
14 sound  
21 sg
180 usb
 Block devices: 块设备
2 fd
8 sd
11 sr   
65 sd
66 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 staff
if 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 number
major=$(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 0
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3
ln -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 4
mknod /dev/${device}pipe1 c $major 5
mknod /dev/${device}pipe2 c $major 6
mknod /dev/${device}pipe3 c $major 7
ln -sf ${device}pipe0 /dev/${device}pipe
chgrp $group /dev/${device}pipe[0-3]
chmod $mode  /dev/${device}pipe[0-3]

rm -f /dev/${device}single
mknod /dev/${device}single  c $major 8
chgrp $group /dev/${device}single
chmod $mode  /dev/${device}single

rm -f /dev/${device}uid
mknod /dev/${device}uid   c $major 9
chgrp $group /dev/${device}uid
chmod $mode  /dev/${device}uid

rm -f /dev/${device}wuid
mknod /dev/${device}wuid  c $major 10
chgrp $group /dev/${device}wuid
chmod $mode  /dev/${device}wuid

rm -f /dev/${device}priv
mknod /dev/${device}priv  c $major 11
chgrp $group /dev/${device}priv
chmod $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 1
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
  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 - 点滴积累

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值