Posts Tagged ‘linux’

你的第一个中断程序!

2 10 月, 2010

Last Update:2011/11/09

本文通过一个简单的中断程序来描述一般中断程序的基本框架。完整代码这里

中断程序一般会包含在某个设备的驱动程序中,因此,接下来的程序本质上还是一个内核模块。说到内核模块,你应该知道首先去看什么了吧?对了,就是内核模块加载函数。

static int __init myirq_init()
{
	printk("Module is working..\n");
	if(request_irq(irq,myirq_handler,IRQF_SHARED,devname,&mydev)!=0)
	{
		printk("%s request IRQ:%d failed..\n",devname,irq);
		return -1;
	}
	printk("%s rquest IRQ:%d success..\n",devname,irq);
	return 0;
}

在内核加载函数中,我们除了显示一些信息外,最重要的工作就是申请一根中断请求线,也就是注册中断处理程序。很明显,这一动作是通过request_irq函数来完成的。这个函数的原型如下:

static int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev);

第一个参数是中断号,这个中断号对应的就是中断控制器上IRQ线的编号。

第二个参数是一个irq_handler_t类型个函数指针:

typedef irqreturn_t (*irq_handler_t)(int, void *);

handler所指向的函数即为中断处理程序,需要具体来实现。

第三个参数为标志位,可以取IRQF_DISABLED、IRQF_SHARED和IRQF_SAMPLE_RANDOM之一。在本实例程序中取IRQF_SHARED,该标志表示多个设备共享一条IRQ线,因此相应的每个设备都需要各自的中断服务例程。一般某个中断线上的中断服务程序在执行时会屏蔽请求该线的其他中断,如果取IRQF_DISABLED标志,则在执行该中断服务程序时会屏蔽所有其他的中断。取IRQF_SAMPLE_RANDOM则表示设备可以被看做是事件随见的发生源。

第四个参数是请求中断的设备的名称。可以在/proc/interface中查看到具体设备的名称,与此同时也可以查看到这个设备对应的中断号以及请求次数,甚至中断控制器的名称。

第五个参数为一个指针型变量。注意此参数为void型,也就是说通过强制转换可以转换为任意类型。这个变量在IRQF_SHARED标志时使用,目的是为即将要释放中断处理程序提供唯一标志。因为多个设备共享一条中断线,因此要释放某个中断处理程序时,必须通过此标志来唯一指定这个中断处理程序。习惯上,会给这个参数传递一个与设备驱动程序对应的设备结构体指针。关于中断程序,可参考这里的文章。

以上就是request_irq函数各个参数的意义。

与中断处理程序的注册相对应的是free_irq函数,它会注销相应的中断处理程序,并释放中断线。这个函数一般被在内核模块卸载函数中被调用。

static void __exit myirq_exit()
{
	printk("Module is leaving..\n");
	free_irq(irq,&mydev);
	printk("%s request IRQ:%d success..\n",devname,irq);
}

如果该中断线不是共享的,那么该函数在释放中断处理程序的同时也将禁用此条中断线。如果是共享中断线,只是释放与mydev对应的中断处理程序。除非该中断处理程序恰好为该中断线上的最后一员,此条中断线才会被禁用。在此处,你也可以感受到mydev的重要性。

下面具体分析中断处理函数。该函数的功能很简单,只是显示一些提示信息。

static irqreturn_t myirq_handler(int irq,void* dev)
{
	struct myirq mydev;
	static int count=1;
	mydev=*(struct myirq*)dev;
	printk("key: %d..\n",count);
	printk("devid:%d ISR is working..\n",mydev.devid);
	printk("ISR is leaving..\n");
	count++;
	return IRQ_HANDLED;
}

另外,本内核模块在插入时还需要附带参数,下面的语句首先定义两个参数,然后利用宏module_param宏来接受参数。

static int irq;
static char* devname;

module_param(devname,charp,0644);
module_param(irq,int,0644);

使用方法:

1.通过cat /proc/interrupts查看中断号,以确定一个即将要共享的中断号。本程序因为是与键盘共享1号中断线,因此irq=1;

2.使用如下命令就可以插入内核:

sudo insmod filename.ko irq=1 devname=myirq

3.再次查看/proc/interrupts文件,可以发现1号中断线对应的的设备名处多了myirq设备名;

4.dmesg查看内核日志文件,可看到在中断处理程序中所显示的信息;

5.卸载内核模块;

可以看到,内核模块加载后,我们所写中断处理程序是被自动调用的,主要是因为该中断线上有键盘所发出的中断请求,因此内核会执行该中断线上的所有中断处理程序,当然就包括我们上述所写的那个中断处理程序。关于中断处理程序的执行,可参考这里的文章。

这样,一个最基本的中断程序就编写完成了!try!

后记:

这个程序调试起来并不难,但是我们并不能仅仅局限在这个程序本身。以它为入口点深入学习中断的基本原理再好不过。下面给出几个学习的入口点。

1.为何我们的中断程序和其他设备共享了一个中断线后会被执行?或者说,共享中断线上的所有中断服务例程是怎么执行的?

2.中断涉及到那些基本的数据结构?这些数据结构之间有什么关系?

3.do_IRQ()函数的大体执行流程是什么?

亲们,要学习的东西还很多,让我们一起加油吧!

什么是中断?

1 10 月, 2010

Last Update:2011/11/03

我们经常听到中断这个词,到底什么是中断?在这之前我先讲给大家一个故事。

从前有两位班主任A和B,A老师带一班,B老师带二班。这两位老师平时都很忙,平时除了为学生们备课改作业,当某个同学提出问题时,还要为他们解答疑问。A老师生怕遗漏每一位同学提出的问题,每隔一段时间就放下手头的工作,不断轮流寻问每一位同学:“你有问题吗”。也许被寻问的这位同学恰好有问题要咨询老师,可这毕竟是少数;而当A老师继续批改作业的时候又出现了一些同学提出问题,可是这个时候还没到A老师轮流寻问同学的时间。就这样,A老师的宝贵时间经常被浪费。

B老师和A老师有所不同,他认为完全没有必要这样死板的寻问每一位同学是否有问题。他对全班同学说:“谁有问题就主动来找我“。即便他可能正在改作业,但是完全可以暂时放下手头那些重要的工作,先为这位迷惑的同学解决问题。因此,B老师既可以改作业,又可以在学生主动提出问题的时候为那个学生解决问题。显然B老师的工作效率比A老师提高了很多。

故事看懂了,那么恭喜你,你也懂了什么是中断。

上面的两位老师分别代表系统中对设备进行管理的两种典型的方式。A老师的那种工作方式属于早期的程序查询控制方式(或称为轮询),内核定期对设备的状态进行查询;而B老师则属于中断控制方式,I/O设备需要服务时,可主动向内核发出中断请求并打断CPU当前正在执行的任务。前者内核为主动;而后者变内核主动为被动,由设备主动向内核发出中断请求。

从物理角度来看,中断请求是由外部硬件设备产生的一种电信号,外部设备首先将这种电信号发给中断控制器,接着中断控制器将此电信号发送给CPU。CPU检查到该中断信号后再通知内核,然后由内核完成后续的一些列处理工作。显然,内核不需要定期去检查设备,从而提高了CPU利用率。

通过以上的描述,你应该对中断有了一个大致的了解,不过想要更具体的了解中断必须搞清楚以下的知识点。

1.中断的分类

早期以及一般情况下,我们所说的中断即指由外设所产生的中断。随着计算机的迅速发展,中断不再仅仅局限于外部设备,CPU本身也会产生中断,不过我们将这种中断称为异常。

对于x86体系结构而言,中断可以分为两大类:同步中断和异步中断。同步中断即我们上面所说的异常,它是由 CPU 在执行非法命令时所产生的。之所以称为同步,是因为这种中断请求信号与代码指令同步执行,也就是说只有在一条指令执行完毕后 CPU 才会发出中断,而不是发生在代码指令执行期间。而异步中断即由外部设备产生的中断,这种中断可以随时发生,习惯上,我们将异步中断仍然称为中断。

中断可分为可屏蔽中断(Maskable interrupt)和不可屏蔽中断(Nomaskable interrupt)。异常可分为故障(fault)、陷阱(trap)、终止(abort)三类。

可屏蔽中断主要是针对外部设备所产生的中断信号,不可屏蔽中断一般是指计算机内部硬件产生的中断。由于异常是CPU发出的中断信号,与中断控制器(下文有解释)无关,因此异常不能被屏蔽。那么,异常和不可屏蔽中断有些相似点:它们均与外部设备无关,并且均不能被屏蔽。

2.中断控制器

中断控制器可分为可编程中断控制器(Programmable Interrupt Controller,PIC)和高级可编程中断控制器(Advanced Programmable Interrupt Controller,APIC)。前者仅可用于单处理器(Uni-processor)平台,后者则可用于多处理器(Mutiliple Processor)平台。

传统的PIC都是通过两片级联的8259A来管理和控制15个由外部设备所产生的中断源。由下图可看到,每片8259A芯片最多可管理8个中断源。但由于两片8259A芯片的级联,即从片的INT输出端与主片的2号(第三条线)中断线相连接,所以总共可以管理15个中断源。

外设和中断控制器上每根相连的线被称为中断线(也称为IRQ线)。对这些中断线进行编号就形成了中断号。IRQn线所对应的中断号即为n。当外部设备产生中断时,就通过中断线向系统发出中断请求(Interrupt ReQuirement)。由于中断控制器只能控制15个中断源,而如今的外设又日益剧增,因此就出现了对各外设共享一条中断线的情况,即中断共享(后文会有详细解释)。

上面已经说过,外部设备的中断请求可以被屏蔽,但是具体体现到硬件上是如何做到的?在每个8259A芯片上都有一个8位的中断屏蔽寄存器,每一位对应一条中断线。在对应为上置1则可屏蔽此条中断线;相反置0则可启动此条中断线。

需要说明的是,现在大多数计算机都使用的是APIC,可以通过查看/proc/interrupts文件获取中断控制器的名称。之所以将传统的8259A作为PIC的举例来学习,最大的原因还是因为它非常的经典,就如同古董——8086处理器一样。通过查看/proc/interrupts文件,可以发现APIC的中断号并不止15个(但即便是这样,还需要共享中断)。

3.中断向量

x86体系结构支持256种中断,即256个中断源。将这些中断源按照0到255的顺序对每种中断进行编号,这个编号叫做中断向量,通常使用8位无符号整数来存储这个向量。中断号和中断向量存在一对一的映射关系。

中断号和中断向量是两个不同的概念。当I/O设备把中断信号发送给中断控制器时,与之关联的是一个中断号;而当中断控制器将该中断信号传递给CPU时,与之关联的是一个中断向量。也就是说,中断号是以中断控制器的角度而言的;而中断向量则是以CPU的角度而言的。中断号和中断向量存在一对一的映射关系。

通常,Intel将编号为0~31的向量分配给异常和非屏蔽中断,这部分向量是固定的。因此在8259A默认情况下,中断号n所对应的中断向量为n+32。

4.中断服务例程

在响应一个具体的中断时,内核会执行一个函数,该函数被称为中断服务例程(Interrupt Service Routine,ISR)。每一个设备的驱动程序中都会定义相关的中断服务例程。从下面的代码可以看到,中断服务例程有两个参数,分别为int型和void指针型。并且返回值为irqreturn_t。

 //linux/include/linux/interrupt.h
  98typedef irqreturn_t (*irq_handler_t)(int, void *);
 //linux/include/linux/irqreturn.h
  10enum irqreturn {
  11        IRQ_NONE,
  12        IRQ_HANDLED,
  13        IRQ_WAKE_THREAD,
  14};
  15
  16typedef enum irqreturn irqreturn_t;

由于irqreturn_t是一个枚举类型,因此本质上为整形。并且IRQ_NONE,IRQ_HANDLED,IRQ_WAKE_THREAD的值一次为0,1,2。IRQ_NONE表示不处理所收到的中断请求;IRQ_HANDLED表示接收到了有效的中断请求,并且作出了正确的处理(这一点在后文中有详细讨论)。

需要注意的是,当一个中断服务例程正在执行的时候,该中断所在所有处理器上的都会被屏蔽,以免继续接受同一条中断线上的另一个新的中断。

以上就是关于中断的基本概念,了解了它们就容易进行后续的中断分析了。

Git快速入门(Q&A)

26 9 月, 2010

如果你对于git完全不知,那么不用着急,我们一起来从零开始学习。

1.难道git就是傻瓜的意思吗?

git是一个版本控制系统,也叫做傻瓜内容跟踪器。按照我目前的理解是:一个项目团队共同完一个项目,而且团队里的每个人都深处异地,那么如何将每个人负责的代码融合在一起?而且如何清晰的展现不同的人在不同的地方在不同的时间里对代码做过修改?这时候,git派上用场了。

事实上,Linux内核正是使用git进行管理的,而git的创始人也正是linux之父:Linus Torvalds。

2.我如何才能获得git?

想使用git,肯定必须安装git了。如果你在ubuntu下,使用下面的命令可以快速安装:sudo apt-get install git-core。安装完成后,可使用git –version查看git的版本号。

3.我在安装完git后该做些什么?

首先你得向git介绍你自己,告诉它你的姓名和邮箱吧。

git config --global user.name "edsionte"
git config --global user.email "edsionte@gmail.com"

4.我依然可以通过男人手册获得git的帮助吗?

当然可以,男人手册是强大的。比如我们可以通过下面的命令获得git中commit命令的相关帮助。

man git-commit

5.我如何开启一个新的管理项目?

比如你要管理的项目目录为mywork,该目录下有一个文件hello.c.进入到该目录,通过git init命令就可以创建一个版本库。hello.c的内容如下:

edsionte@edsionte-desktop:~/mywork$ cat hello.c
#include “stdio.h”

int main()
{
	printf("hello,git!\n");
	printf("I can use git!\n");
	return 0;
}

6.如何将我的文件加入到版本库?

比如我要将hello.c文件加入到我的版本库,那么我可以这样:

git add hello.c
git commit

第一条命令只是将hello.c文件增加到版本库的索引当中,而第二条命令才真正的将hello.c文件的内容提交到版本库中,并且当你执行第二条命令的时候会在终端出现新的界面,在这个界面中你可以输入与该文件相关的开发日志等,比如我们输入“The version is: 0.0.1”。注意,要在这个界面下进行相关操作必须以ctrl组合相应的字母。

事实上第二条命令我们可以这样处理:

git commit -m "The version is: 0.0.1"

即直接在命令中输入开发日志信息。

每当你改进原有文件的代码时,你都可以按照上面的指令将更新后的内容添加到版本库中。不过,当我们只是更新了原有文件的内容而不是新增文件或者目录时,就可以用下面的命令代替以上工作:

git commit -a

7.在我对某个(些)文件进行了修改之后,我如何查看新旧文件的差异?

这是一个好问题。不过在回答这个问题之前,请先回顾如何将一个文件更新到版本库:先修改文件,再add,再commit。OK,那么接下来的几个指令的使用时间就很清晰了。

在add之前,可以使用git diff命令来查看文件被更新前后的差异。

在coomit之前,可以使用git diff –cached命令来查看文件被更新前后的差异。

以上命令可以查看文件内容的具体差异。那么在coomit之前,如何查看那些文件被修改过?git status即可做到。

8.既然我记录了开发日志,那么我如何查看它?

git log

9.什么是分支?分支有何用?

以上我们的操作均是在主分支master中进行的。有时候我们需要创建一个个人分支,以避免对主分支的影响;有时候我们需要一个临时性的分支去完成一些实验性的工作;当出现以上情况时,我们就需要创建一个分支。

10.如何创建一个分支?对于这个分支我能做些什么?

如果我想创建temp分支,那么可以通过git branch temp来完成。 通过git branch命令可以查看所以分支,比如:

edsionte@edsionte-desktop:~/mywork$ git branch
* master
  slave
  temp

而且*标志表示当前你所在的分支。如果你想进入temp分支,那么通过git checkout temp命令完成。接下来你再temp分支中创建新文件或者修改原有文件都不会影响主分支。

11.到目前为止,我认为temp分支完全正确,那么如何将它合并到主分支?

在这之前你要确认一下temp分支和master分支中现在存在那些文件。在master分支中有一个hello.c文件。在temp分支中当然也存在hello.c文件,不过被修改成如下内容:

edsionte@edsionte-desktop:~/mywork$ cat hello.c
#include “stdio.h”

int main()
{
	printf("hello,git!\n");
	printf("I can use git!\n");
	printf("I love git!\n");
	return 0;
}

此外,在temp分支中还新建了hi.c文件。使用git commit -a命令向版本库中提交了hello.c文件后,还必须add和commit来提交新建的hi.c文件,完成后切换到master分支。接着使用git merge temp命令就可将temp分支合并到主分支了。查看hello.c文件,是不是已经更新了?而且还新增了hi.c文件。

edsionte@edsionte-desktop:~/mywork$ git merge temp
Merge made by recursive.
 hello.c |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

12.虽然你说的很轻松,可是我在试验的时候还是出现了诸如下面的错误,这是怎么回事?

edsionte@edsionte-desktop:~/mywork$ git merge temp
Auto-merging hello.c
CONFLICT (content): Merge conflict in hello.c
Automatic merge failed; fix conflicts and then commit the result.

如果出现这样的情况,我们可以看一下master分支下的hello.c文件:

edsionte@edsionte-desktop:~/mywork$ cat hello.c
#include “stdio.h”

int main()
{
	printf("hello,git!\n");
<<<<<<< HEAD ======= 	printf("I can use git!\n"); >>>>>>> temp
	return 0;
}

“====”符号是出现混淆内容的分割线。“<<<<<<< HEAD”与”====”之间的语句段为空,而”====”与“>>>>>>> temp”之间的语句段为printf(“I can use git!\n”);。出现这种情况是因为git不能辨别这两段语句的前后关系,这时候需要我们人为来调整分支文件的内容再合并。

13.接下来我还能做些什么?

以上的内容只是git的简单入门教程,更多的内容可以参考git男人手册

register_chrdev_region函数源码分析

21 9 月, 2010

如何找到一个有效的切入点去深入分析内核源码,这是一个令人深思的问题。本文以前文中未详细说明的函数为切入点,深入分析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

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

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是一个链接文件。每次系统访问软连接文件时,就会自动去访问它所链接的那个源文件。当源文件本身被删除后,这个链接文件也就失效了,尽管它还存在,但是已经不能访问到源文件了。

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