1、主设备号和此设备号
    主编号标识设备相连的驱动,次编号被内核用来决定引用哪个设备。
    在内核中, dev_t 类型(<linux/types.h>中定义)用来持有设备编号。对于 2.6.0 内核, dev_t 32 位的量, 12 位用作主编号, 20 位用作次编号.
        应当利用在 <linux/kdev_t.h>中的一套宏定义. 为获得一个 dev_t 的主或者次编号, 使用:

 (dev_t)-->主设备号、次设备号

 MAJOR(dev_t dev)
 MINOR(dev_t dev)

 主设备号、次设备号-->(dev_t)

 MKDEV(int major,int minor) 

 

    在建立一个字符驱动时你的驱动需要做的第一件事是获取一个或多个设备编号来使用。并且应当在不再使用它们时释放它。

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);      //
释放设备编号

安排主编号最好的方式, 我们认为, 是缺省使用动态分配, 而留给自己在加载时或者甚至在编译时指定主编号的选项权.

以下是在scull.c中用来获取主设备好的代码:

 

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

 

动态分配的缺点是你无法提前创建设备节点, 因为分配给你的模块的主编号会变化. 对于驱动的正常使用, 这不是问题, 因为一旦编号分配了, 你可从 /proc/devices 中读取它.

2、一些重要数据结构

大部分的基础性的驱动操作包括 4 个重要的内核数据结构cdev,file_operations, file inode.

file_operations结构定义在 <linux/fs.h>

 

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,定义于 <linux/fs.h>与用户空间程序的 FILE 指针没有任何关系。文件结构代表一个打开的文件.它由内核在 open 时创建, 并传递给在文件上操作的任何函数, 直到最后的关闭. 在文件的所有实例都关闭后, 内核释放这个数据结构.

struct inode ,是在内核内部用来表示文件的。因此, 它和代表打开文件描述符的文件结构是不同的. 可能有代表单个文件的多个打开描述符的许多文件结构, 但是它们都指向一个单个 inode 结构.

struct cdev ,Linux2.6内核与2.4内核不同2.6内核采用了。cdev结构体来描述管理字符设备

struct cdev {
 struct kobject kobj; //嵌在cdev结构中的kobject对象
 struct module *owner;
 struct file_operations *ops;/*file_operation 结构体,最终与硬件打交道的函数都注册在这里*/
 struct list_head list;
 dev_t dev;
 unsigned int count;
};
与其相关的操作函数有:
    1. struct cdev 分配空间(如果已经将struct cdev 嵌入到自己的设备的特定结构体中,并分配了空间,这步略过!)

struct cdev *my_cdev = cdev_alloc();

    1. 初始化struct cdev

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

    1. 初始化cdev.owner

cdev.owner = THIS_MODULE;

    1. cdev设置完成,通知内核struct cdev的信息(在执行这步之前必须确定你对struct cdev的以上设置已经完成!

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

从系统中移除一个字符设备:void cdev_del(struct cdev *p)

以下是scull中的初始化代码(之前已经为struct scull_dev 分配了空间):

 

/*
 * Set up the char_dev structure for this device.
 */

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;  //
这句可以省略,在cdev_init中已经做过
    err
= cdev_add (&dev->cdev, devno, 1);
    
/* Fail gracefully if need be
这步值得注意*/
    
if (err)
        printk
(KERN_NOTICE "Error %d adding scull%d", err, index);
}

open 方法

 

open 方法提供给驱动来做任何的初始化来准备后续的操作. 在大部分驱动中, open 应当进行下面的工作:

●检查设备特定的错误(例如设备没准备好, 或者类似的硬件错误)
如果它第一次打开, 初始化设备
如果需要, 更新 f_op 指针.
分配并填充要放进 filp->private_data 的任何数据结构

但是, 事情的第一步常常是确定打开哪个设备. 记住 open 方法的原型是:

 

int (*open)(struct inode *inode, struct file *filp);

 

inode 参数有我们需要的信息,以它的 i_cdev 成员的形式, 里面包含我们之前建立的 cdev 结构.

 

container_of(pointer, container_type, container_field);

 

这个宏使用一个指向 container_field 类型的成员的指针, 它在一个 container_type 类型的结构中, 并且返回一个指针指向包含结构. scull_open, 这个宏用来找到适当的设备结构:

 

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

 

识别打开的设备的另外的方法是查看×××存储在 inode 结构的次编号. 如果你使用 register_chrdev 注册你的设备, 你必须使用这个技术. 确认使用 iminor inode 结构中获取次编号, 并且确定它对应一个你的驱动真正准备好处理的设备.

 

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) {
scull_trim(dev); /* ignore errors */
}
return 0; /* success */
}

 

 

release 方法

●释放 open 分配在 filp->private_data 中的任何东西
在最后的 close 关闭设备

scull 的基本形式没有硬件去关闭, 因此需要的代码是最少的:

 

int scull_release(struct inode *inode, struct file *filp)
{
return 0;
}

scull模型的内存使用