字符设备驱动分析(2)

24 9 月, 2010 by edsionte 无评论 »

前文中,我们按照一般内核模块的结构分析了globalmem_init函数和globalmem_exit函数。通过上述两个函数可以完成字符驱动的加载和卸载。那么本文将进一步分析字符设备驱动的实现。

linux2.6内核中使用cdev结构体来表述一个字符设备驱动,但是一般我们并不直接使用cdev结构体,而是将与该设备相关的信息与cdev街头体结合爱一起,定义一个新的结构体,比如

struct globalmem_dev
{    struct cdev cdev;
      unsigned char mem[GLOBALMEM_SIZE];
};
struct cdev
{
	struct kobject kobj;//内嵌kobject对象
	struct module *owner;//指向实现驱动程序的模块的指针,通常为THIS_MODULE
	const struct file_operations *ops;//指向此设备驱动程序文件操作结构体的指针
	struct list_head list;//指向字符设备文件对应的索引节点链表的头
	dev_t dev;//设备号
	unsigned int count;//给该设备驱动程序分配的设备号范围的大小
};

就像前文所说的,设备号都是分配一个范围(count的大小),因此可能有很多个设备文件主设备号相同并且对应于同一个设备驱动。list所指向的链表就是由当前该设备驱动对应的设备文件索引节点组成。

我们现在回到globalmem_setup_cdev函数,它的主要作用就是申请并初始化一个cdev结构体,并且将通过cdev_add函数向系统内添加一个cdev,完成字符设备的注册。通常我们将cdev_add函数安排在字符设备驱动模块的加载函数中,而对应的将cdev_del函数放在字符设备驱动的卸载函数中。

static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);

cdev_init(&dev->cdev, &globalmem_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding cdev%d", err, index);
}

除此之外,globalmem_setup_cdev函数还会将cdev结构体中的struct file_operation类型的指针ops实例化。globalmem_fops全局变量是文件操作表,这个结构中含有许多文件操作函数类型的指针。当我们实现某些文件操作函数时,就可以将这些函数名赋值给这个结构中的相应变量。比如我们在稍候会实现globalmem_open函数,将其赋值给globalmem.open,那么当用户使用open系统调用对字符设备文件进行打开操作时,内核就会自动调用适合该设备文件的打开函数,也就是globalmem_open函数。

正如你所知的那样,Linux下一些皆为文件,当然设备也不例外。对于一个设备文件来说,用户通过VFS可以使用统一的系统调用接口对各种设备(文件)进行相关操作,比如open,read,write等等,用户可以不去考虑当前设备具体如何去操作。而在VFS层下——位于操作系统中的设备驱动就会对于每种设备去实现相应的操作函数。对于每类设备所实现的操作如何在用户层统一的表现出来,这就需要struct file_operations结构体。此结构体中包含大量的函数指针,这些函数指针便是用户层上统一的系统调用函数名,将设备驱动中实现的具体操作函数赋值给这些函数指针后,用户就可以使用统一的系统调用函数了。

接下来我们来看具体的文件操作函数是如何实现的。
文件打开函数将设备结构体指针赋值给私有数据,这个私有数据会在稍候的read以及write中被用到,而不是直接的使用globalmem_devp。

/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
  /*将设备结构体指针赋值给文件私有数据指针*/
  filp->private_data = globalmem_devp;
  return 0;
}

在读函数中,首先将私有数据赋值给一个设备结构体指针。然后,判断要读的长度是否合法。接着利用copy_to_user函数内核空间的数据(dev->mem)拷贝到用户空间。关于这个copy_to_user函数的详细拷贝过程,我们也可以对其进行代码分析。如果拷贝成功,那么修改相应的指针即可完毕读操作。

/*读函数*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
  loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/

  /*分析获取有效的写长度*/
  if (p >= GLOBALMEM_SIZE)
    return count ?  - ENXIO: 0;
  if (count > GLOBALMEM_SIZE - p)
    count = GLOBALMEM_SIZE - p;

  /*从内核空间向用户空间写数据*/
  if (copy_to_user(buf, (void*)(dev->mem + p), count))
  {
    ret =  - EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;

    printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
  }

  return ret;
}

写函数与读函数的过程大体一直,不同的是使用了copy_from_user函数。这里不再详解。

接下来我们就可以使用一个简单测试程序来对我们所实现的字符设备驱动进行测试了。

register_chrdev_region函数源码分析

21 9 月, 2010 by edsionte 7 comments »

如何找到一个有效的切入点去深入分析内核源码,这是一个令人深思的问题。本文以前文中未详细说明的函数为切入点,深入分析char_dev.c文件的代码。如果你已经拥有了C语言基础和一些数据结构基础,那么还等什么?Let’s go!

在《字符设备驱动分析》一文中,我们说到register_chrdev_region函数的功能是在已知起始设备号的情况下去申请一组连续的设备号。不过大部分驱动书籍都没有去深入说明此函数,可能是因为这个函数内部封装了__register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name)函数的原因。不过我们不用苦恼,这正好促使我们去分析这个函数。

194int register_chrdev_region(dev_t from, unsigned count, const char *name)
 195{
 196        struct char_device_struct *cd;
 197        dev_t to = from + count;
 198        dev_t n, next;
 199
 200        for (n = from; n <\ to; n = next) {
 201                next = MKDEV(MAJOR(n)+1, 0);
 202                if (next >\ to)
 203                        next = to;
 204                cd = __register_chrdev_region(MAJOR(n), MINOR(n),
 205                               next - n, name);
 206                if (IS_ERR(cd))
 207                        goto fail;
 208        }
 209        return 0;
 210fail:
 211        to = n;
 212        for (n = from; n <\ to; n = next) {
 213                next = MKDEV(MAJOR(n)+1, 0);
 214                kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
 215        }
 216        return PTR_ERR(cd);
 217}

首先值得我们注意的是,这个函数每次分配的是一组设备编号。其中from参数是这组连续设备号的起始设备号,count是这组设备号的大小(也是次设备号的个数),name参数处理本组设备的驱动名称。另外,当次设备号数目过多(count过多)的时候,次设备号可能会溢出到下一个主设备。因此我们在for语句中可以看到,首先得到下一个主设备号(其实也是一个设备号,只不过此时的次设备号为0)并存储于next中。然后判断在from的基础上再追加count个设备是否已经溢出到下一个主设备号。如果没有溢出(next小于to),那么整个for语句就只执行个一次__register_chrdev_region函数;否则当设备号溢出时,会把当前溢出的设备号范围划分为几个小范围,分别调用__register_chrdev_region函数。

如果在某个小范围调用__register_chrdev_region时出现了失败,那么会将此前分配的设备号都释放。

其实register_chrdev_region函数还没有完全说清除设备号分配的具体过程,因为具体某个小范围的设备号是由__register_chrdev_region函数来完成的。可能你已经注意到在register_chrdev_region函数源码中出现了struct char_device_struct结构,我们首先来看这个结构体:

  50static struct char_device_struct {
  51        struct char_device_struct *next;
  52        unsigned int major;
  53        unsigned int baseminor;
  54        int minorct;
  55        char name[64];
  56        struct cdev *cdev;              /* will die */
  57} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

在register_chrdev_region函数中,在每个字符设备号的小范围上调用__register_chrdev_region函数,都会返回一个struct char_device_struct类型的指针。因此我们可以得知,struct char_device_struct类型对应的并不是每一个字符设备,而是具有连续设备号的一组字符设备。从这个结构体内部的字段也可以看出,这组连续的设备号的主设备号为major,次设备号起始为baseminor,次设备号范围为minorct,这组设备号对应的设备驱动名称为name,cdev为指向这个字符设备驱动的指针。

这里要特别说明的是,内核中所有已分配的字符设备编号都记录在一个名为chrdevs散列表里。该散列表中的每一个元素是一个 char_device_struct结构,这个散列表的大小为255(CHRDEV_MAJOR_HASH_SIZE),这是因为系统屏蔽了12位主设备号的前四位。既然说到散列表,那么肯定会出现冲突现象,因此next字段就是冲突链表中的下一个元素的指针。

接下来我们详细来析__register_chrdev_region函数。首先为cd变量分配内存并用零来填充(这就是用kzalloc而不是kmalloc的原因)。接着通过P操作使得后续要执行的语句均处于临界区。

  92static struct char_device_struct *
  93__register_chrdev_region(unsigned int major, unsigned int baseminor,
  94                           int minorct, const char *name)
  95{
  96        struct char_device_struct *cd, **cp;
  97        int ret = 0;
  98        int i;
  99
 100        cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
 101        if (cd == NULL)
 102                return ERR_PTR(-ENOMEM);
 103
 104        mutex_lock(&chrdevs_lock);

如果major为0,也就是未指定一个具体的主设备号,需要动态分配。那么接下来的if语句就在整个散列表中为这组设备寻找合适的位置,即从散列表的末尾开始寻找chrdevs[i]为空的情况。若找到后,那么i不仅代表这组设备的主设备号,也代表其在散列表中的关键字。当然,如果主设备号实现已指定,那么可不去理会这部分代码。

 105
 106        /* temporary */
 107        if (major == 0) {
 108                for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
 109                        if (chrdevs[i] == NULL)
 110                                break;
 111                }
 112
 113                if (i == 0) {
 114                        ret = -EBUSY;
 115                        goto out;
 116                }
 117                major = i;
 118                ret = major;
 119        }

接着对将参数中的值依次赋给cd变量的对应字段。当主设备号非零,即事先已知的话,那么还要通过major_to_index函数对其进行除模255运算,因此整个散列表关键字的范围是0~254。

 120
 121        cd->major = major;
 122        cd->baseminor = baseminor;
 123        cd->minorct = minorct;
 124        strlcpy(cd->name, name, sizeof(cd->name));
 125
 126        i = major_to_index(major);

至此,我们通过上面的代码会得到一个有效的主设备号(如果可以继续执行下面代码的话),那么接下来还不能继续分配。正如你所知的那样,散列表中的冲突是在所难免的。因此我们得到major的值后,我们要去遍历冲突链表,为当前我们所述的char_device_struct类型的变量cd去寻找正确的位置。更重要的是,我们要检查当前的次设备号范围,即baseminor~baseminor+minorct,是否和之前的已分配的次设备号(前提是major相同)范围有重叠。

下面的for循环就是在冲突链表中查找何时的位置,当出现以下三种情况时,for语句会停止。

(1)如果冲突表中正被遍历的结点的主设备号(*(cp)->major)大于我们所分配的主设备号(major),那么就可以跳出for语句,不再继续查找。此时应该说设备号分配成功了,那么cd结点只需等待被插到冲突链表当中(*cp节点之前)。

(2)如果(*cp)结点和cd结点的主设备号相同,但是前者的次设备号起点比cd结点的大,那么跳出for语句,等待下一步的范围重叠的检测。

(3)如果(*cp)结点和cd结点的主设备号相同,但是cd结点的次设备号起点小于(*cp)结点的次设备号的终点,那么会跳出for语句。此时很可能两个范围的次设备号发生了重叠。

由上面的分析可以看出,冲突表中是按照设备号递增的顺序排列的。

 127
 128        for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
 129                if ((*cp)->major > major ||
 130                    ((*cp)->major == major &&
 131                     (((*cp)->baseminor >= baseminor) ||
 132                      ((*cp)->baseminor + (*cp)->minorct > baseminor))))
 133                        break;

接下来检测当主设备号相同时,次设备范围是否发生了重叠。首先依次计算出新老次设备号的范围,接着进行范围判断。第一个判断语句是检测新范围的终点是否在老范围的之间;第二个判断语句是检测新范围的起点是否在老范围之间。

 134
 135        /* Check for overlapping minor ranges.  */
 136        if (*cp && (*cp)->major == major) {
 137                int old_min = (*cp)->baseminor;
 138                int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
 139                int new_min = baseminor;
 140                int new_max = baseminor + minorct - 1;
 141
 142                /* New driver overlaps from the left.  */
 143                if (new_max >= old_min && new_max <= old_max) {
 144                        ret = -EBUSY;
 145                        goto out;
 146                }
 147
 148                /* New driver overlaps from the right.  */
 149                if (new_min <= old_max && new_min >= old_min) {
 150                        ret = -EBUSY;
 151                        goto out;
 152                }
 153        }

当一切都正常后,就将char_device_struct描述符插入到中途链表中。至此,一次小范围的设备号分配成功。并且此时离开临界区,进行V操作。如果上述过程中有任何失败,则会跳转到out处,返回错误信息。

 154
 155        cd->next = *cp;
 156        *cp = cd;
 157        mutex_unlock(&chrdevs_lock);
 158        return cd;
 159out:
 160        mutex_unlock(&chrdevs_lock);
 161        kfree(cd);
 162        return ERR_PTR(ret);
 163}

至此,我们已经分析完了字符设备号分配函数。

软链接和硬链接

20 9 月, 2010 by edsionte 无评论 »

硬链接类似与一个指向文件的指针(但是与文件描述符不同),比如我们通过下面命令:

edsionte@edsionte-laptop:~$ touch file1
edsionte@edsionte-laptop:~$ ln file1 file1hdlink
edsionte@edsionte-laptop:~$ ls -l file1 file1hdlink
-rw-r--r-- 2 edsionte edsionte 0 2010-09-20 22:56 file1
-rw-r--r-- 2 edsionte edsionte 0 2010-09-20 22:56 file1hdlink
edsionte@edsionte-laptop:~$ rm file1
edsionte@edsionte-laptop:~$ ls -l file1hdlink
-rw-r--r-- 1 edsionte edsionte 0 2010-09-20 22:56 file1hdlink

通过ln命令我们为file1文件创建了一个硬链接file1hdlink。通过ls -li 命令我们也可以发现,这两个文件的索引节点,属性以及大小均均是相同的,因此我们可以得出这样的结论:file1和file1hdlink同时指向一个文件(类似指针),它们只是同一个文件的两个不同名字而已。此时也就没有file1是源文件,而file1hdlink是硬链接这样的概念了,两者的地位相同。当我们删除其中一个文件时,就会发现连接数减少了一个。当某个文件的链接数为0时,这个文件便会被删除。

软链接也叫符号链接(symbol link),它相当于windows下快捷方式。与硬链接不同的是,软链接本身就是一类文件(链接文件),因此软链接本身的索引结点和其链接的文件的索引结点是不同的。比如通过下面的命令:

edsionte@edsionte-laptop:~$ ln -s file2 file2symlink
edsionte@edsionte-laptop:~$ ls -li file2 file2symlink
51217 -rw-r--r-- 1 edsionte edsionte 7 2010-09-20 23:18 file2
51214 lrwxrwxrwx 1 edsionte edsionte 5 2010-09-20 23:17 file2symlink -> file2
edsionte@edsionte-laptop:~$ cat file2symlink
hello!
edsionte@edsionte-laptop:~$ rm file2
edsionte@edsionte-laptop:~$ cat file2symlink
cat: file2symlink: 没有那个文件或目录

这里我们也可以发现file2的链接数为1。这里的两个文件并不是平等的关系,从文件属性也可以发现file2是普通文件,file2symlink是一个链接文件。每次系统访问软连接文件时,就会自动去访问它所链接的那个源文件。当源文件本身被删除后,这个链接文件也就失效了,尽管它还存在,但是已经不能访问到源文件了。

Software Freedom Day

19 9 月, 2010 by edsionte 无评论 »

今天去新校区参加了软件自由日西邮站,深深被xiyoulinux小组的每位成员所感动。特别是关于小组成员在准备此次活动时的一些影像,看着那些熟悉的面孔在为今天的自由日活动做着准备,心里很是感动,自愧不如。那种感动应该是他们对Linux的狂热、对理想的坚持。他们身上散发的open精神是无人能及的,只有当你真正融入这个小小的团体,才能感受到他们巨大的力量。

加油,xiyouLinux Group!

字符设备驱动分析(1)

16 9 月, 2010 by edsionte 2 comments »

Last Update:9/24(斜体字为更新内容)

Last Update:9/20

Last Update:9/19

熟悉了模块编程的基本框架后,我们就可以试着分析一个简单的字符设备驱动。下面以《设备驱动开发详解》一书中的代码6.17为例来分析这个字符设备驱动的代码。

我们现在对于对前文中hello,kernel内核模块进行稍微的改动。我们都知道内核模块的入口函数是module_init(function name)内注册的函数。也就是告诉内核“从这个函数入口”。那么我们分析字符设备驱动模块,首先应该去看globalmem_init函数。

module_init(globalmem_init);
module_exit(globalmem_exit);

在globalmem_init函数中,首先通过宏MKDEV获得32位的设备驱动号。通常linux中的设备号由主设备号和次设备号组成,主设备号对应每一类设备,而次设备号对应该类设备的具体一个设备。dev_t类型前12位是主设备号(但事实上主设备号的前四位被屏蔽了,如果去看源码就可得知),后20位为次设备号。由于可以事先指定主设备号globalmem_major,因此我们需要用宏MKDEV来获得dev_t类型的设备号:

#define MINORBITS       20
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

这个宏通过移位和或运算,巧妙的得到dev_t类型的设备号。这个宏可以在include/linux/kdev_t.h中查找到。网上有的资料中会给出MINORBITS为8,这个应该是适合16位的设备号的情况。

接着通过全局变量globamem_major来判断是否事先分配了起始的设备号。如果是,则继续分配连续的一段设备号,否则动态分配设备号,并且通过MAJOR宏获得分配以后的主设备号。这里需要强调的是,下面的两种设备号分配函数,都是一次性分配一组连续的设备号(当然也可以只分配一个设备号,调整参数即可)。

首先我们分析已知起始设备号的情况。通过调用register_chrdev_region函数,便可以申请到一组连续范围的设备号。在linux/fs/char_dev.c中可以看到此函数的原型。

 int register_chrdev_region(dev_t from, unsigned count, const char *name);

其中from是首设备号,而count是这组连续设备号的数目。name为设备名。而《设备》一书中的count为1,也就是说,这组设备号的数量为1。

其次,当起始设备号并未指定时就要动态的申请了,使用下面的函数:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);

与上面静态分配函数不同的是,此时dev是一个指针类型,因为要返回将要分配的设备号。关于以上两个函数的内核源码分析,具体参见这里

以上都成功执行后,我们给globalmem_devp指针分配内存空间。其中globalmem_devp是一个指向globalmem设备结构体的指针。我们通过mmset对此块内存空间进行初始化之后。接着globalmem_setup_cdev函数对cdev初始化并将此字符设备注册到内核。

//设备驱动模块加载函数
int globalmem_init(void)
{
	int result;
	dev_t devno=MKDEV(globalmem_major,0);

	if(globalmem_major)
	{
		result=register_chrdev_region(devno,1,"globalmem");
	}
	else
	{
		result=alloc_chrdev_region(&devno,0,1,"globalmem");
		globalmem_major=MAJOR(devno);
	}
	if(result<0)
		return result;
	globalmem_devp=kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL);
	if(!globalmem_devp)
	{
		result=-ENOMEM;
		goto fail_malloc;
	}

	memset(globalmem_devp,0,sizeof(struct globalmem_dev));

	globalmem_setup_cdev(globalmem_devp,0);
	return 0;
fail_malloc:unregister_chrdev_region(devno,1);
	return result;
}

上面的程序已经很“整齐”的说明了init函数的主要作用。具体如下:

1.申请设备号

2.为设备相关的数据结构分配内存

3.初始化并注册cdev(globalmem_setup_cdev函数实现)

有了字符设备驱动的加载函数,那么肯定有卸载函数:

void globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev);
kfree(globalmem_devp);
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}

总体来看,globalmem_init函数完成的是字符设备的一些初始化工作,以及向系统内注册。而globalmem_exit就是进行字符设备的释放工作:从内核中删除这个字符设备,释放设备结构体所占的内存,以及释放申请的设备号。从结构上看,它并没有偏离内核模块编程的结构范围,仍然是我们熟悉的hello,kernel。

我们在此先暂时将globalmem_setup_cdev函数内部的实现用return 0;来代替。那么我们现在用插入内核模块的命令将我们这个字符设备驱动(尽管许多功能还未实现)插入到内核中。成功后我们可以通过cat /proc/devices 命令来查看刚刚字符设备名称以及主设备号。/proc是一个虚拟的文件系统,这里的虚拟是指这个文件系统并不占用磁盘空间而只存在于内存中。而该目录下的devices文件中存储着系统字符和块设备的驱动名称以及设备编号。

接下来,我们可以通过:mknod /dev/globalmem c 250 0命令创建一个设备节点。有了这个设备节点后,就可以对它进行类似普通文件那样的操作了(当然现在还不能,因为并未实现具体的操作函数)。

这样一个字符驱动的大致上有了雏形。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key