linux设备分为:字符设备和块设备。
字符设备:就是那些像键盘那样以字符为单位、逐个字符进行输入/输出的设备。
块设备:像磁盘那样以块或扇区为单位、成块进行输入/输出的设备。
之所以把设备分为字符设备和块设备是因为一方面描述比较方便,另一方面是为技术上方便。
建立一个设备可以通过mknod()系统调用实现。比如mknod /dev/mycdev c 123 0
其中c代表字符设备,123代表主设备号,0代表次设备号。一个设备可以通过主设备号+次设备号唯一确定。
載内核里面使用cedv结构描述一个字符设备,其具体定义如下:
2 struct cdev {
3 struct kobject kobj;
4 struct module *owner;
5 const struct file_operations *ops;
6 struct list_head list;
7 dev_t dev;
8 unsigned int count;
9 };
其中kobj为内嵌的kobject对象,module为所属模块,字段file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数,dev_t定义了dev设备号,为32位,其中12位为主设备号,而20位次设备号。我们可以使用宏MAJOR(dev_t dev)和MINOR(dev_t dev)获得主设备号和次设备号。
内核中提供了一组用于操作cdev结构体的函数,如下:
21 void cdev_init(struct cdev *, const struct file_operations *);
22
23 struct cdev *cdev_alloc(void);
24
25 void cdev_put(struct cdev *p);
26
27 int cdev_add(struct cdev *, dev_t, unsigned);
28
29 void cdev_del(struct cdev *);
30
31 void cd_forget(struct inode *);
32
33 extern struct backing_dev_info directly_mappable_cdev_bdi;
cdev_init用于初始化cdev成员,具体定义如下:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
543 {
544 memset(cdev, 0, sizeof *cdev);
545 INIT_LIST_HEAD(&cdev->list);
546 kobject_init(&cdev->kobj, &ktype_cdev_default);
547 cdev->ops = fops;
548 }
注意:Initializes @cdev, remembering @fops, making it ready to add to the
system with cdev_add().
cdev_alloc()函数用于动态申请一个cdev内存,具体定义如下:
524 struct cdev *cdev_alloc(void)
525 {
526 struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
527 if (p) {
528 INIT_LIST_HEAD(&p->list);
529 kobject_init(&p->kobj, &ktype_cdev_dynamic);
530 }
531 return p;
532 }
申请成功就会返回cdev结构体大小的空间,如果失败就返回NULL。
cdev_put用于生成一个模块
void cdev_put(struct cdev *p)
358 {
359 if (p) {
360 struct module *owner = p->owner;
361 kobject_put(&p->kobj);
362 module_put(owner);
363 }
364 }
cdev_add()函数向系统添加一个cdev。,具体定义如下:
472 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
473 {
474 p->dev = dev;
475 p->count = count;
476 return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
477 }
cdev_del()函数向系统删除一个cdev。,具体定义如下:
void cdev_del(struct cdev *p)
492 {
493 cdev_unmap(p->dev, p->count);
494 kobject_put(&p->kobj);
495 }
cdev_add()函数和cdev_del()函数完成了字符设备的注册和注销。对cdev_add()函数调用通常发生在字符设备驱动的加载函数中,而对cdev_del()函数的使用通常发生載字符设备驱动模块的卸载函数中。載调用cdev_add()函数向系统注册字符设备之前,硬先调用register_chrdev_region()或alloc_chrdev_region()函数向系统申请设备号。
字符设备驱动模块除了加载和卸载部分,最主要的就是file_operations结构体中成员函数的实现。
包括open()、read()、write()、release()等等。
具体实现如下:
static int mycdev_open(struct inode *inode, struct file *fp)
{
return 0; /*由系统实现open操作*/
}
static int mycdev_release(struct inode *inode, struct file *fp)
{
return 0; /*由系统实现release操作*/
}
static ssize_t mycdev_read(struct file *fp, char __user *buf, size_t size, loff_t *pos)
{
unsigned long p = *pos;
unsigned int count = size;
char kernel_buf[MYCDEV_SIZE] = "This is mycdev!";
if(p >= MYCDEV_SIZE)
return -1;
if(count > MYCDEV_SIZE-p)
count = MYCDEV_SIZE - p;
if (copy_to_user(buf, kernel_buf, count) != 0) {
printk("read error!\n");
return -1;
}
} /*read中copy_to_user操作实现了从内核向用户态空间的拷贝*/
write操作和读操作差不多,它通过copy_from_user实现了从用户空间向内核空间的拷贝。
实现了上述函数之后要 定义一个file_operations实例。并将具体设备驱动的函数赋值给file_operations的成员。具体模板如下:
static const struct file_operations mycdev_fops =
{
.owner = THIS_MODULE,
.read = mycdev_read,
.write = mycdev_write,
.open = mycdev_open,
.release = mycdev_release,
};
好了具体字符设备驱动模块讲的差不多了,你可以动手实现你的模块了。哦对了,模块写好后,还需要写一个用户态程序验证。
阅读(1287) | 评论(0) | 转发(0) |