Linux内核大讲堂 (二) 传说中的字符设备(2)

Linux内核大讲堂 () 传说中的字符设备(2)

       这一节我们先给出一个字符设备的小例子,源码结构如下:

|-- wwhs_chardev

|   |-- Makefile

|   |-- wwhs_chardrv.c      //驱动

|   `-- wwhs_chardrv_test.c  //小测试程序

`-- wwhs_public.h

请大家养成看Makefile的好习惯,这个Makefile很简单的。

先生成字符设备的驱动,字符设备驱动会在/dev/目录下生成一个wwhs_chardev的节点。然后再生成测试小程序,小测试程序就是打开这个节点,然后往节点里写几个字符,然后再读出来。

先给出源码吧:

Makefile:

obj-m+=wwhs_chardrv.o

KERNELDIR=/lib/modules/$(shell uname -r)/build

PWD:=$(shell pwd)

all:chardrv test

 

chardrv:

       make -C $(KERNELDIR) M=$(PWD) modules

test:

       gcc wwhs_chardrv_test.c -g -o wwhs_chardrv_test

clean:

       rm -rf *.o* *.ko* *.mod.c *.cmd *.symvers .tmp_versions .*.cmd wwhs_chardrv_test

 

wwhs_chardrv.c:

 

#include "../wwhs_public.h"

 

#define WWHS_MAJOR 247

#define WWHS_CHARDEV_NAME "wwhs_chardev"

#define WWHS_MAX_BUFFER 256

#define WWHS_CLASS_NAME "wwhs_class"

 

 

 

static int wwhs_open(struct inode *inode, struct file *file)

{

       printk("%s/n",__func__);

       return 0;

}

 

static char *wwhs_charname;

 

ssize_t wwhs_read(struct file *file, char __user *user, size_t size, loff_t *offset)

{

       int wwhs_size = strlen(wwhs_charname) + 1;

      

       if(copy_to_user(user,wwhs_charname,wwhs_size))

              return 0;

       *offset = wwhs_size;

       printk("%s/n",__func__);

       return wwhs_size;

}

 

ssize_t wwhs_write(struct file *file, char __user *user, size_t size, loff_t *offset)

{

       if (size < 1)

              return -EINVAL;

      

       if (wwhs_charname)

              kfree(wwhs_charname);

      

       wwhs_charname =kzalloc(size*sizeof(char),GFP_KERNEL);

       if (!wwhs_charname){  

              printk("out of memory/n");

              return -ENOMEM;

       }

      

       if(copy_from_user(wwhs_charname,user,size))

              return -EFAULT;

       *offset += size;

 

       printk("%s/n",__func__);

       return size;

}

 

static const struct file_operations wwhs_fops = {

       .owner  = THIS_MODULE,

       .open   = wwhs_open,

       .read   = wwhs_read,  

       .write  = wwhs_write,

};

 

static struct class *wwhs_class;

 

static int __init wwhs_init()

{

       wwhs_class = class_create(THIS_MODULE, WWHS_CLASS_NAME);

       device_create(wwhs_class, NULL, MKDEV(WWHS_MAJOR, 0), NULL, WWHS_CHARDEV_NAME);

       register_chrdev(WWHS_MAJOR, WWHS_CHARDEV_NAME, &wwhs_fops);    

       return 0;

}

 

static void __exit wwhs_exit()

{

       if (wwhs_charname)

              kfree(wwhs_charname);

       device_destroy(wwhs_class,MKDEV(WWHS_MAJOR, 0));     

       unregister_chrdev(WWHS_MAJOR,WWHS_CHARDEV_NAME);

       class_destroy(wwhs_class);

}

 

module_init(wwhs_init);

module_exit(wwhs_exit);

MODULE_AUTHOR("wwhs");

MODULE_DESCRIPTION("wwhs_chardev");

MODULE_LICENSE("GPL");

 

wwhs_chardrv_test.c:

#include

#include

#include

#include

 

 

 

#define WWHSCHARDEVPATH "/dev/wwhs_chardev"

#define CONTEXT "mychardevnamewwhsfdfasf124123434"

int main()

{

       int fd = -1;

       int ret = 0;

       char buf[128] = {0};

       fd = open(WWHSCHARDEVPATH,O_RDWR);

       if (fd < 0) {

              ret = -1;

              goto error;

       }

      

       if (write(fd,CONTEXT,sizeof(CONTEXT)) < 0 ){

              ret = -1;

              goto ferror;

       }

       if (read(fd,buf,sizeof(CONTEXT)) < 0 ){

              ret = -1;

              goto ferror;

       }

 

ferror:

       close(fd);

error:

       printf("buf:%s/n",buf);

       return ret;

}

 

 

wwhs_public.h:

 

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

 

#define wwhs_dbg(dbgbuf) printk(KERN_ERR"wwhs:%s/n",dbgbuf);

 

进入wwhs_chardrv目录然后:

make

insmod wwhs_chardrv.ko

ls /dev/wwhs/wwhs_chardev –l

输出如下:

[root@localhost wwhs_chardev]# ls /dev/wwhs_chardev -l

crw------- 1 root root 247, 0 May 25 15:58 /dev/wwhs_chardev

要先解释一下这中间有驱动相关的两个参数,看到2470了吧?这就是传说中的主设备号和从设备号。关于这个玩意可是有来头的,内核为了管理这两位大侠可谓是耗费精力,有拆开他们,合并他们,有自动申请。。。。可以说这两位大侠在内核中的地位是非常高的。接下来我们就分析一下这两位大侠。

class__register()之前已经分析过了。

device_create()因为之前我偷懒,所以有的东西没有讲,这一节我补上一点点,其实之前我交代过各位同学请自行分析的,不知道大家有没有分析,如果没有分析的话,这一节我帮大家补充一下。

device_create()->device_register()->device_add()->devtmpfs_create_node()->vfs_mknod().

这个玩意就是以前各位玩过字符设备同志的最爱:mknod命令的变种。

所以以后可以不用那么土了,我们应该尽量将与驱动相关的代码统一起来在内核里面管理,只要代码写的好,完全可以把所有的任务全都在内核中处理。一者效率高,二者维护也更方便,一抓一大陀,多爽!但是现实永远没有那么美好,半调子驱动工程师写出的人不人鬼不鬼的代码,给新手留下的印象都是一堆堆相互之间没有联系的东西,形成一些所谓的“经验”。从此,大家都觉得“经验”很重要,有的东西人家不说你不知道,很多工程师把mknod这个命令封到库里面,搞的好像很高深一样,真的觉得挺好玩的。一言以概之,什么档次的人就玩什么档次的花样!

OK,劳骚也发了,打击的人肯定是一大片,但哥不在乎,哥说的是自已内心真实的想法,只要能帮助曾经和我一样迷茫的你,就够了,再大的砖头砸下来,哥也扛得住。

好了,我们继续分析一下这个传说中的东西。

vfs_path_lookup(dev_mnt->mnt_root, dev_mnt,

                           nodename, LOOKUP_PARENT, &nd);

dev_mnt->mnt_root看到没有?

很明显,你已经据有初始化意识了,这个玩意就是等价于/dev的。所以你创建的nd的父目录就是它。很明显你创建的节点会在/dev目录下。

大概搞清了这一条主线了!我们在/dev目录下创建了一个具有自定义名称的字符设备节点,这个字符设备拥有我们分配的主从设备号。

接下来我们回到register_chrdev()

先给出函数原型:

static inline int register_chrdev(unsigned int major, const char *name,

                              const struct file_operations *fops)

{

       return __register_chrdev(major, 0, 256, name, fops);

}

int __register_chrdev(unsigned int major, unsigned int baseminor,

                    unsigned int count, const char *name,

                    const struct file_operations *fops)

{

       struct char_device_struct *cd;

       struct cdev *cdev;

       int err = -ENOMEM;

 

       cd = __register_chrdev_region(major, baseminor, count, name);

       if (IS_ERR(cd))

              return PTR_ERR(cd);

      

       cdev = cdev_alloc();

       if (!cdev)

              goto out2;

 

       cdev->owner = fops->owner;

       cdev->ops = fops;

       kobject_set_name(&cdev->kobj, "%s", name);

             

       err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);

       if (err)

              goto out;

 

       cd->cdev = cdev;

 

       return major ? 0 : cd->major;

out:

       kobject_put(&cdev->kobj);

out2:

       kfree(__unregister_chrdev_region(cd->major, baseminor, count));

       return err;

}

首先是:

cd = __register_chrdev_region(major, baseminor, count, name);

这个函数主要是申请了一个char_device_struct结构体,然后根据major生成的indexchrdevs这个哈希指针数组中索引到对应的指针,进行一系列的出错检查后,再执行:

cd->next = *cp;

*cp = cd;

来实现两者的绑定,并返回cd这个结构体指针。

接下来就是cdev_alloc(),这个没太多好讲的,就是分配了一个cdev的结构体,进行初始化后再返回结构体的指针。

接下来实现一些我们都很熟悉的操作:

cdev->owner = fops->owner;

cdev->ops = fops;            //最重要的就是这一步了。

kobject_set_name(&cdev->kobj, "%s", name);

接下来就把cdev和用MKDEV生成的包含有主设备号信息的dev_t等一起用参数传进cdev_add()函数中。

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

{

       p->dev = dev;

       p->count = count;

       return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);

}

int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,

            struct module *module, kobj_probe_t *probe,

            int (*lock)(dev_t, void *), void *data)

{

       unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;

       unsigned index = MAJOR(dev);

       unsigned i;

       struct probe *p;

 

       if (n > 255)

              n = 255;

 

       p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);

 

       if (p == NULL)

              return -ENOMEM;

 

       for (i = 0; i < n; i++, p++) {

              p->owner = module;

              p->get = probe;

              p->lock = lock;

              p->dev = dev;

              p->range = range;

              p->data = data;

       }

       mutex_lock(domain->lock);

       for (i = 0, p -= n; i < n; i++, p++, index++) {

              struct probe **s = &domain->probes[index % 255];

              while (*s && (*s)->range < range)

                     s = &(*s)->next;

              p->next = *s;

              *s = p;

       }

       mutex_unlock(domain->lock);

       return 0;

}

上一节我们对cdev_map已经做了分析,这个函数没啥别的,就是把cdev等信息与cdev_map中的probe相关联。不过提醒一下probe又是一个链表。

大概如下图所示:

画的比较挫,我也是尝试一下,结果折腾了半天才折腾出这样一个鸟图。我狂郁闷。

再解释一下算了,probesprobe指向了我们动态分配到的probeporbe有三个重要的成员。Next就是指向下一个probe的指针,形成一个链,而dev_t就是主从设备号的杂种。Data是一个void*指针,指向我们最重要的cdevcdev中又包含我们在例子程序中写的wwhs_fop

就这么简单,下一节,我们会继续讲解openead等是怎么调用我们实际写的驱动中的openread函数。受不了了,得抽烟了。下节见。^_^

 

 

你可能感兴趣的:(linux内核大讲堂系列)