如果大家没意见的话,我们继续usb_stor_acquire_resources函数.
761至764行,这没啥好说的吧.就是刚才urb申请了之后判断是否申请成功了,如果指针为NULL那么就是失败了.直接返回-ENOMEM.别往下了.
767行,哦,又一个家伙闪亮登场了,dev_semaphore,这是一个信号量,在storage_probe的最初始阶段我们曾经见过,当时有这么一句话,这就是调用一个宏init_MUTEX来初始化一个信号量,
943 init_MUTEX(&(us->dev_semaphore));
我们当时说了,等到用的时候再讲.不过现在的确是到用的时候了,不过,我还不想讲.曾经我天真的以为,只要学了谭浩强的那本C程序设计,即便不能写代码,也应该能够看懂代码.然而,后来我发现事实并非如此,世界没错,我错了.
首先,什么是信号量?毕业的时候,校园招聘中常常笔试面试会考信号量,会考死锁,不过通常考的会比较简单,更多的情况是考察一些基本概念,印象中曾经在Sun中国工程研究院的面试中被问起过,当然也有比较复杂一点的,要求结合具体的问题对某种算法进行合理性分析,比如Intel某年在上海交大的笔试题,考到了经典的哲学家进餐问题,不过幸运的是这种变态的笔试题我没遇上,我进Intel的时候只被问起解释冒泡排序法...
我们整个故事中有两个信号量,它们都是us的成员,一个是这个dev_semaphore,另一个是sema,在定义struct us_data的时候我们已经看到过,
111 struct semaphore dev_semaphore; /* protect pusb_dev */
156 struct semaphore sema; /* to sleep thread on */
sema也同样是在storage_probe的一开始就做了初始化,
944 init_MUTEX_LOCKED(&(us->sema));
设备驱动中最难的部分在于三个方面,一个是涉及到内存管理的代码,一个是涉及到进程管理的代码,另一个就是信号量和互斥锁或者别的锁的代码.这些部分如果不合理将容易导致系统崩溃,而信号量最容易导致的就是死锁.
网名为卖血上网的哥们说话了,那么到底什么是信号量?或者什么是互斥锁?
先说互斥锁.它诞生于这样一个背景.这个世界上,有些东西只能属于某一个人,或者说在一个时间里只能属于一个人,这你承认吧,比如一个女孩的心.当你要追求一个女孩时,你首先会去了解其人是否名花有主,若是否,你才会去追求,若已然有主,那么你只能放弃,或者准确的说,你只能等待.当然你可以很激昂的说,如果等待可以换来奇迹的话,我宁愿等下去,哪怕一年,抑或一生!然而,她爱的是他,终究不是你,所以你伤悲,流泪,却打不开她心中那把锁.这里你应该就能感觉到什么是互斥锁了,一个女孩如果心有所属,那么对你来说,就仿佛已有人在你之前给她上了一把锁,而钥匙,不在你这里.
那么你问代码中为什么要锁?Ok,我告诉你,如果你正在操作一个队列,比如一个队列一百个元素,你想把第七十个读出来,于是你去遍历这个队列,可是如果没有锁,那么可能你遍历的时候别人也可以操作这个队列,比如你马上就要找到第七十个了,可是,可是,注意了,可是,这个时候,说不定哪位哥们儿缺德,把第七十个数给删除了,那你不是白忙活了?所以,怎么办?设一个锁,每个人要想操作这个队列就得先获得这把锁,而一旦你获得了这把锁,你在操作这把锁期间,别人就不能操作,因为他要操作他就得先获得锁,而这个时候锁在你这里,所以他只能等待,等你结束了,释放了锁,他才可以去操作.那么互斥锁指的就是这种情况,一个资源只能同时被一个进程操作,互斥的字面意思也正是如此.互相排斥,就像爱情是自私的一样.
那什么是信号量?信号量和互斥锁略有不同.它允许一个时间里有几个进程同时访问一段资源.到底允许几个可以设定,这称为设定信号量的初值,如设定为1,那就说明是同一时间只有一个进程可以访问,那么这就是互斥锁了,不过有的时候一个资源确实可以让几个进程访问,特别是读访问,你想一个文件可以被两个进程同时读,这不要紧吧,各读各的,谁也影响不了谁,只要大家都不写就是了.设定信号量的初值,比如设定5,那么就是说,同一时间你就让最多5个进程同时去读这个文件.每个进程获取了信号量就把信号量的值减一,到第六个进程了,它去判断,发现值已经等于0了,于是它不能访问了,只能等待,等待别的进程释放锁.
不过也许,一把钥匙只能开一把锁,更能代表爱情的专一.所以实际上,Linux内核代码中锁用得更多,而初值不为1的信号量用得相对来说不多.比如我们刚才看到的这两个信号量,都是属于当作互斥锁在用.因为他们的初值一个被设置成了1(init_MUTEX),一个被设置成了0(init_MUTEX_LOCKED).设置成一很好理解,就是一把互斥锁,只能容许一个进程去获得它,设置成0呢,就表示这把锁已经被锁住了,得有进程释放它才可以.我们这里767行这句down(&us->dev_semaphore)和up(&us->dev_semaphore)是一对,一个是获得锁,一个是释放锁.它们就是为了保护中间那段代码.我刚才说我不想讲这段代码,的确,现在讲为什么这里要用锁还为时过早,整个故事中us->dev_semaphore出现的次数不是很多,但是我们必须对整个代码都熟悉了才可能理解为什么要用这把锁,因为这些代码都是环环相扣的,不能孤立来看.所以,我们将在故事的收尾阶段来一次性来以高屋建瓴的方式看每一处down(&us->dev_semaphore)和up(&us->dev_semaphore)的使用原因.接下来我们看到了这把锁的时候,也将一并跳过不提.到最后再来看.需要说的是这里down和up这两个函数的作用分别就是去获得锁和释放锁.对于down来说,它每次判断一下信号量的值是否大于0,若是,就进入下面的代码,同时将信号量的值减一,若否,就等待,或者说专业一点,进入睡眠.对于down(),我们小时候那部<<挥剑问情>>的歌词很形象的描述了其行为,
男:挥剑问情,如果问是有情,也愿以身相许,以身相殉;
女:挥剑问情,如果问是无情,又怕回首别是一种伤心.
不过,我们这里看到了两把锁,除了us->dev_semaphore以外,另一个是us->sema,现在还没有使用它,但是我们可以先说一下,us->sema在整个故事中出现的次数不多,总共只有三次.加上这里提到的这句初始化为0的语句,一个出现了四次.所以我们在遇到它的时候不需要跳过,会详细的讲.因为很容易理解它为什么会用在那里.请你记住,这个信号量或者说这把锁是被初始化为0了,不是1,它一开始就处于锁住的状态,到时候你就会知道为什么了.我们边走边看.
另外一个需要说一下的是,与down ()相似的有一个叫做down_interruptible ()的函数,它们的区别在于后者可被信号打断,而前者不可被信号打断,而一旦问是无情,那么他们将进入等待,或者专业一点说,进入睡眠,直到某一天...
我们将看到的获取us->sema的函数正是down_interruptible.而释放锁的函数仍然可以用up.