`
javababy1
  • 浏览: 1169460 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

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 <stdio.h>

#include <unistd.h>

#include <fcntl.h>

#include <sys/types.h>

#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 <linux/module.h>

#include <linux/device.h>

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/stat.h>

#include <linux/slab.h>

#include <linux/kobject.h>

#include <linux/klist.h>

#include <linux/kdev_t.h>

#include <linux/fs.h>

#include <linux/major.h>

#include <linux/kprobes.h>

#include <asm/uaccess.h>

#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函数。受不

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics