前段时间转载了一篇“字符设备模型浅析”,此乃大神之作(在“好文推荐”目录中),膜拜许久,感觉不够,所以转载了。做个基层官员混口饭吃没关系,如果想进入核心领导层,想攀升,就必须理解背后的机制,背后十年功呀。希望想深入研究设备驱动注册机理的同志们分析下。我在这里之分析下几个注册函数,写个例子应用下,跟前者相比太献丑了,希望坚持带来提高,呵呵。
我先简单介绍下字符设备驱动的概念。
1、字符设备可以分为字符设备, 块设备,网络设备。
字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、磁带驱动器、鼠标等。块设备可以用任意顺序进行访问,以块为单位进行操作,如硬盘、软驱等。字符设备不经过系统的快速缓冲,而块设备经过系统的快速缓冲。但是,字符设备和块设备并没有明显的界限,如对于Flash设备,符合块设备的特点,但是我们仍然可以把它作为一个字符设备来访问。
2、前面有一篇我讲到了应用程序如何最终调用设备驱动程序。现在我们看看字符设备驱动是如何注册的,如果想搞清楚这个问题,我还是建议看我转载的那篇文章。
(1)分配一个设备号,如果自己不知道哪些设备号空闲,可调用动态分配函数。
静态分配函数:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
使用:指定从设备号from开始,申请count个设备号,在/proc/devices中的名字为name。返回值:成功返回0,失败返回错误码。
动态分配函数:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
使用:动态申请从次设备号baseminor开始的count个设备号,在/proc/devices中的名字为name,并通过dev指针把分配到的设备号返回给调用函数者。返回值:成功返回0,失败返回错误码。
(2)注册设备。
首先分配一个cdev结构体
然后调用:void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数:cdev:之前我定义的cdev结构体;fops:设备对应的文件操作结构体。
返回值:(函数有可能失败,查看返回值是必须的)成功返回0,示范返回对应的错误码
这个函数将cdev与file_operations联系起来,我们还要手动指定test_cdev->owner = THIS_MODULE.这样内核能帮助我们维护cdev结构。
- void cdev_init(struct cdev *cdev, const struct file_operations *fops)
- 529{
- 530 memset(cdev, 0, sizeof *cdev);
- 531 INIT_LIST_HEAD(&cdev->list);
- 532 cdev->kobj.ktype = &ktype_cdev_default;
- 533 kobject_init(&cdev->kobj);
- 534 cdev->ops = fops;
- 535}
然后添加设备到内核:int cdev_add(struct cdev *cdev, dev_t dev, unsigned count)
这个函数非常可能失败,所以我们要做相应的处理。有兴趣的可以看下cdev_add的源码。
(3)当设备驱动程序不用时,需卸载:注销设备所使用的设备号,void unregister_chrdev_region(dev_t from, unsigned count)注销cdev结构体void cdev_del(struct cdev *p)
3 我觉得有必要介绍下老的设备注册方法。
- int register_chrdev(unsigned int major, const char *name,
- 267 const struct file_operations *fops)
- 268{
- 269 struct char_device_struct *cd;
- 270 struct cdev *cdev;
- 271 char *s;
- 272 int err = -ENOMEM;
- 273
- 274 cd = __register_chrdev_region(major, 0, 256, name);
- 275 if (IS_ERR(cd))
- 276 return PTR_ERR(cd);
- 277
- 278 cdev = cdev_alloc();
- 279 if (!cdev)
- 280 goto out2;
- 281
- 282 cdev->owner = fops->owner;
- 283 cdev->ops = fops;
- 284 kobject_set_name(&cdev->kobj, "%s", name);
- 285 for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
- 286 *s = '!';
- 287
- 288 err = cdev_add(cdev, MKDEV(cd->major, 0), 256);
- 289 if (err)
- 290 goto out;
- 291
- 292 cd->cdev = cdev;
- 293
- 294 return major ? 0 : cd->major;
- 295out:
- 296 kobject_put(&cdev->kobj);
- 297out2:
- 298 kfree(__unregister_chrdev_region(cd->major, 0, 256));
- 299 return err;
- 300}
仔细看下代码会发现,原来老的注册方法是我们讲的新方法的封装,它虽然在某种意义上带来了便利,但是也存在很多麻烦。对它的调用将为给定的主设备号注册0-255作为此设备号。使用这一接口必须能够处理256个此设备号的open调用,而且也不能使用大于255的测设备号。新的内核不主张使用这一接口。
下面参考书上写的一个简单的驱动程序,在系统上测试过。
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/cdev.h>
- #include <linux/fs.h>
- #include <linux/types.h>
- #include <linux/slab.h>
- #include <linux/uaccess.h>
- #include <linux/moduleparam.h>
- //#include <linux/>
- MODULE_LICENSE("Dual BSD/GPL");
- int major=0;
- module_param(major,int,0);
- int minor=0;
- module_param(minor,int,0);
- struct test_dev{
- char test[256];
- struct cdev cdev;
- };
- struct test_dev* my_dev;
- int test_open(struct inode *inode,struct file *filp)
- {
- printk(KERN_ALERT "open, tamclub");
- return 0;
- }
- int test_release(struct inode *inode,struct file *filp)
- {
- printk(KERN_ALERT "close, tamclub");
- return 0;
- }
- static struct file_operations test_fops={
- .owner=THIS_MODULE,
- .open=test_open,
- .release=test_release,
- };
- static int hello_init(void)
- {
- int ret;
- dev_t dev;
- my_dev=kmalloc(sizeof(struct test_dev),GFP_KERNEL);
- if(!my_dev){
- printk("bad kmalloc");
- return -ENOMEM;
- }
- if(major){
- dev=MKDEV(major,minor);
- ret=register_chrdev_region(dev,0,"test");
- }else{
- ret=alloc_chrdev_region(&dev,minor,1,"test");
- major=MAJOR(dev);
- printk(KERN_ALERT "%d",major);
- }
- if(ret<0){
- printk(KERN_ALERT "can't get major %d\n",major);
- }
- cdev_init(&my_dev->cdev,&test_fops);
- my_dev->cdev.owner=THIS_MODULE;
- my_dev->cdev.ops=&test_fops;
- ret=cdev_add(&my_dev->cdev,dev,1);
- if(ret){
- printk(KERN_ALERT "error %d",ret);
- }
-
- printk(KERN_ALERT "Hello, tmaclub\n");
- return 0;
- }
- static void hello_exit(void)
- {
- unregister_chrdev_region(MKDEV(major,minor),1);
- cdev_del(&my_dev->cdev);
- kfree(my_dev);
- printk(KERN_ALERT "Goodbye, tmaclub\n");
- }
- module_init(hello_init);
- module_exit(hello_exit);
下面是makefile:
- ifeq ($(KERNELRELEASE),)
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- PWD := $(shell pwd)
- modules:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- modules_install:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
- .PHONY: modules modules_install clean
- else
- # called from kernel build system: just declare what our modules are
- obj-m :=my_dev.o
- endif
调试过程:
首先执行make
然后执行sudo insmod my_dev.ko,执行dmesg观察信息:[ 207.350969] 252<1>Hello, tmaclub
创建dev文件:mknod /dev/my_dev c 252 0(252是自动分配的主设备号,有printk打印出)
最后执行cat /dev/my_dev
完成后执行dmesg观察内核的打印信息:[ 300.442444] open, tamclub<1>close, tamclub
调试完成。
阅读(2197) | 评论(0) | 转发(0) |