LDD3学习笔记(二)--简单的字符设备
2010-09-21 20:30
176 查看
通过老大的提示,自己的努力,完成了第三章的学习,最后自己实现了一个类似书本scull字符设备驱动模块。
什么叫字符设备,什么叫字符设备驱动。字符设备和字符设备驱动是两个不同的概念。字符设备就是以字节为单位进行顺序访问的一类设备的总称。典型的常用的字符设备有:键盘,串口,控制台等。字符设备驱动程序就是提供操作字符设备的机制。
一、主设备号与次设备号
在自己的系统上输入: ls -l /dev观察输出。我们会发现如下面所示的文件的详细信息。
view plaincopy to clipboardprint?
crw-r--r-- 1 root root 4, 0 6月 26 2010 systty
crw-rw-rw- 1 root tty 5, 0 6月 26 2010 tty
crw--w---- 1 root root 4, 0 6月 26 2010 tty0
crw--w---- 1 root root 4, 1 6月 26 2010 tty1
crw--w---- 1 root tty 4, 10 6月 26 2010 tty10
crw--w---- 1 root tty 4, 11 6月 26 2010 tty11
分配多个设备编号时,分配到的编号的主设备号都是一样的。所以有时也说成是分配主设备号,其实我我也想知道分配函数的源代码,可是我不知道它在哪里定义,也不知道怎么找。
四、动态分配主设备号
我自己也真想知道是分配了主设备号再得到设备号,还是分配设备才得到主设备号。反正传出参数dev_t类型,意思是说参数是设备编号。当然这不影响我们的学习,但是知道个所以然是最好的,好读书并求甚解是最好的。
关于选择静态还是动态分配的讨论书本说的比较清楚,并且也很容易看懂,那就多看看书。一般我们采用动态的分配的方式。作者提供的相关源代码我认为非常经典了。我们在完成字符设备编号的申请完全可以只做些变量名的修改就可以使用了。
view plaincopy to clipboardprint?
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;
}
字符设备的注册分三步:一、定义字符设备结构。二、初始化。三、添加字符设备到内核中。下面我们一步步地理解。
根据分配和初始化字符设备结构的不同,有两种不同的方式。
方法一、
1、动态获得字符设备结构并初始化
view plaincopy to clipboardprint?
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = *my_fops;
my_cdev->ower = THIS_MODULE;
2、把设备好的cdev结构添加到内核中去:
view plaincopy to clipboardprint?
int cdev_add(struct cdev *dev, dev_t num; unsigned int count);
初始化设备用到了一个函数:cdev_init()看了原型就知道为什么那句可以省掉了。
原型:
view plaincopy to clipboardprint?
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
定义了一个scull_fops结构,然后用这个结构初始化scull_dev这个设备的struct cdev结构成员。有个成员:owner不是一个操作函数指针,关于它的说明请参考书本,几乎所有的情况下它都要被初始化成THIS_MODLE.其实书本的的例子是把struct cdev这个内核表示的字符设备结构嵌入到scull这个设备里。所以它struct cdev的ops成员的方式如下:
view plaincopy to clipboardprint?
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);
}
第一个参数第二个参数不就是我们介绍的那两个成员吗?
还有file结构。我们说过file结构表示打开的文件。这个结构是内核在打开设备文件的时候创建的,也就是说,内核在其内部用file结构表示每一个打开的文件,所以file结构是内核创建的,不用在驱动程序里定义,也不可能在驱动程序里定义,只有内核才有创建这个结构的能力。关于file结构的内核也是我们必须要了解的。我们再仔细看struct file_operations各函数指针成员,那些函数的参数都包含:strict inode,struct file 这两个参数。而且我们要记住这两个参数是由内核传给驱动模块的。来看具体的例子:
view plaincopy to clipboardprint?
int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev;
dev = container_of(inode->i_cdev, struct scull_dev,cdev);
filp->private_data = dev;
return 0;
}
内核在处理这个调用的时候大概会做这些工作:
一、调用system_call来处理系统调用open,老实处理的具体细节我也不懂,呵呵。
二、通过/dev/scull0这个设备文件的主设备号找到驱动程序,通过设备文件的inode的dev_t i_rdev和struct cdev *i_cdev找到具体的设备
三、通过驱动程序模块和设备结构的ops(file_operations)调用scull_open
四、返回filp
描述得比较粗糙,这是因为我本身理解得也比较粗糙,还请高手指点,当然如果理解错误并误人子弟,我表示歉意。
这里省略了file_operations结构成员的编写,其实它是一个驱动程序最重要的部分。但由于每个人每个驱动提供的机制不一样,编写这个结构的成员的方法各不一样。总的来说,你要驱动程序提供什么机制就编写对应的成员,而每个成员都有一个具体的系统调用与其对应。理解了字符设备驱动程序的结构后再理解scull设备就比较容易了。书本的scull设备的结构总的来说还是比较复杂的,所以其file_operations对于一个学习驱动程序的新手来说还是比较难理解的。但是我相信,理解了书本上讲到的理论后再去读源代码应该比较容易,然后再反过来去理解理论就可以比较深入地理解那些原理了。
附:
一、这是我们老大在我们学习时提出的几点要求,大家也可以参考,按着这些要求去看书,去学习。
1, 理解什么是字符设备
2,字符设备的主设备号,次设备号,内核使用主设备号,找到驱动模块,而次设备号,通过什么途径传递给驱动模块代码使用的。
3,怎样自己创建设备节点, mknod的使用方法
4,主设备号可以预先固定,也可以通过alloc_chrdev_region动态获得的。驱动中是怎么注册字符设备的。思考一下注册的操作,内核会做哪 些工作
可以通过cat /proc/devices获得装载的模块主设备号,可以通过一个脚本,读取这个文件,获得主设备号,并通过mknod创建设备节点。
5,字符设备的file_operations包括那些成员函数指针,这些指针在应用中是怎么对应使用的。
6,尝试写一个字符设备驱动,理解每个kernel API的用法和含义。
什么叫字符设备,什么叫字符设备驱动。字符设备和字符设备驱动是两个不同的概念。字符设备就是以字节为单位进行顺序访问的一类设备的总称。典型的常用的字符设备有:键盘,串口,控制台等。字符设备驱动程序就是提供操作字符设备的机制。
一、主设备号与次设备号
在自己的系统上输入: ls -l /dev观察输出。我们会发现如下面所示的文件的详细信息。
view plaincopy to clipboardprint?
crw-r--r-- 1 root root 4, 0 6月 26 2010 systty
crw-rw-rw- 1 root tty 5, 0 6月 26 2010 tty
crw--w---- 1 root root 4, 0 6月 26 2010 tty0
crw--w---- 1 root root 4, 1 6月 26 2010 tty1
crw--w---- 1 root tty 4, 10 6月 26 2010 tty10
crw--w---- 1 root tty 4, 11 6月 26 2010 tty11
view plaincopy to clipboardprint? 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); 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);
分配多个设备编号时,分配到的编号的主设备号都是一样的。所以有时也说成是分配主设备号,其实我我也想知道分配函数的源代码,可是我不知道它在哪里定义,也不知道怎么找。
四、动态分配主设备号
我自己也真想知道是分配了主设备号再得到设备号,还是分配设备才得到主设备号。反正传出参数dev_t类型,意思是说参数是设备编号。当然这不影响我们的学习,但是知道个所以然是最好的,好读书并求甚解是最好的。
关于选择静态还是动态分配的讨论书本说的比较清楚,并且也很容易看懂,那就多看看书。一般我们采用动态的分配的方式。作者提供的相关源代码我认为非常经典了。我们在完成字符设备编号的申请完全可以只做些变量名的修改就可以使用了。
view plaincopy to clipboardprint?
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;
}
view plaincopy to clipboardprint? struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; }; struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count;};
字符设备的注册分三步:一、定义字符设备结构。二、初始化。三、添加字符设备到内核中。下面我们一步步地理解。
根据分配和初始化字符设备结构的不同,有两种不同的方式。
方法一、
1、动态获得字符设备结构并初始化
view plaincopy to clipboardprint?
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = *my_fops;
my_cdev->ower = THIS_MODULE;
view plaincopy to clipboardprint? struct cdev *cdev_alloc(void) { struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(&p->list); kobject_init(&p->kobj, &ktype_cdev_dynamic); } return p; } struct cdev *cdev_alloc(void){ struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(&p->list); kobject_init(&p->kobj, &ktype_cdev_dynamic); } return p;}
2、把设备好的cdev结构添加到内核中去:
view plaincopy to clipboardprint?
int cdev_add(struct cdev *dev, dev_t num; unsigned int count);
view plaincopy to clipboardprint? 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); } 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);}
初始化设备用到了一个函数:cdev_init()看了原型就知道为什么那句可以省掉了。
原型:
view plaincopy to clipboardprint?
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
view plaincopy to clipboardprint? 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_operations scull_fops{ .owner = THIS_MODULE, .llseek = scull_llseek, .read = scull_read, .write = scull_write, .ioctl = scull_ioctl, .open = scull_open, .release = scull_release,};
定义了一个scull_fops结构,然后用这个结构初始化scull_dev这个设备的struct cdev结构成员。有个成员:owner不是一个操作函数指针,关于它的说明请参考书本,几乎所有的情况下它都要被初始化成THIS_MODLE.其实书本的的例子是把struct cdev这个内核表示的字符设备结构嵌入到scull这个设备里。所以它struct cdev的ops成员的方式如下:
view plaincopy to clipboardprint?
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);
}
view plaincopy to clipboardprint? int cdev_add(struct cdev *dev, dev_t num, unsigned int count), int cdev_add(struct cdev *dev, dev_t num, unsigned int count),
第一个参数第二个参数不就是我们介绍的那两个成员吗?
还有file结构。我们说过file结构表示打开的文件。这个结构是内核在打开设备文件的时候创建的,也就是说,内核在其内部用file结构表示每一个打开的文件,所以file结构是内核创建的,不用在驱动程序里定义,也不可能在驱动程序里定义,只有内核才有创建这个结构的能力。关于file结构的内核也是我们必须要了解的。我们再仔细看struct file_operations各函数指针成员,那些函数的参数都包含:strict inode,struct file 这两个参数。而且我们要记住这两个参数是由内核传给驱动模块的。来看具体的例子:
view plaincopy to clipboardprint?
int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev;
dev = container_of(inode->i_cdev, struct scull_dev,cdev);
filp->private_data = dev;
return 0;
}
view plaincopy to clipboardprint? int fd; fd = open("/dev/scull0", buf,5); //这个是系统调用open,请参考它的用法。 int fd;fd = open("/dev/scull0", buf,5); //这个是系统调用open,请参考它的用法。
内核在处理这个调用的时候大概会做这些工作:
一、调用system_call来处理系统调用open,老实处理的具体细节我也不懂,呵呵。
二、通过/dev/scull0这个设备文件的主设备号找到驱动程序,通过设备文件的inode的dev_t i_rdev和struct cdev *i_cdev找到具体的设备
三、通过驱动程序模块和设备结构的ops(file_operations)调用scull_open
四、返回filp
描述得比较粗糙,这是因为我本身理解得也比较粗糙,还请高手指点,当然如果理解错误并误人子弟,我表示歉意。
这里省略了file_operations结构成员的编写,其实它是一个驱动程序最重要的部分。但由于每个人每个驱动提供的机制不一样,编写这个结构的成员的方法各不一样。总的来说,你要驱动程序提供什么机制就编写对应的成员,而每个成员都有一个具体的系统调用与其对应。理解了字符设备驱动程序的结构后再理解scull设备就比较容易了。书本的scull设备的结构总的来说还是比较复杂的,所以其file_operations对于一个学习驱动程序的新手来说还是比较难理解的。但是我相信,理解了书本上讲到的理论后再去读源代码应该比较容易,然后再反过来去理解理论就可以比较深入地理解那些原理了。
附:
一、这是我们老大在我们学习时提出的几点要求,大家也可以参考,按着这些要求去看书,去学习。
1, 理解什么是字符设备
2,字符设备的主设备号,次设备号,内核使用主设备号,找到驱动模块,而次设备号,通过什么途径传递给驱动模块代码使用的。
3,怎样自己创建设备节点, mknod的使用方法
4,主设备号可以预先固定,也可以通过alloc_chrdev_region动态获得的。驱动中是怎么注册字符设备的。思考一下注册的操作,内核会做哪 些工作
可以通过cat /proc/devices获得装载的模块主设备号,可以通过一个脚本,读取这个文件,获得主设备号,并通过mknod创建设备节点。
5,字符设备的file_operations包括那些成员函数指针,这些指针在应用中是怎么对应使用的。
6,尝试写一个字符设备驱动,理解每个kernel API的用法和含义。
相关文章推荐
- LDD3学习笔记(二)--简单的字符设备
- LDD3《Linux设备驱动》中的最简单的字符设备驱动实现与测试
- Linux 字符设备驱动开发基础(二)—— 编写简单 PWM 设备驱动
- 最简单的字符设备驱动gobalvar
- 自己编程实现简单的字符设备架构
- 【NanoPi Duo试用体验】+一个简单的字符设备驱动
- Linux 字符设备驱动简单总结
- Linux内核开发之简单字符设备驱动(上)
- linux设备驱动程序之简单字符设备驱动
- 简单字符设备应用
- linux设备驱动程序之简单字符设备驱动
- [笔记]:简单的globalmem字符设备驱动移植过程
- 一个简单的字符设备驱动程序和测试程序
- linux简单的字符设备驱动程序
- Linux字符设备驱动的简单总结
- Linux内核开发之简单字符设备驱动(下)
- Linux内核开发之简单字符设备驱动(下)
- linux设备驱动第三篇:如何写一个简单的字符设备驱动?
- linux设备驱动第三篇:写一个简单的字符设备驱动
- 简单字符设备驱动流程