Linux那些事儿之我是U盘(34)彼岸花的传说(二)

本文深入解析了usb_storage守护线程的核心代码,揭示了守护线程的工作机制和重要性。从死循环的设计理念到关键函数的实现细节,全面介绍了usb_storage守护线程如何确保USB存储设备的稳定运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果让观众短信投票的话,usb_stor_control_thread()这个函数中的代码无疑是整个模块中最为精华的代码.我们只需要它中间301行那个for(;;)就知道,这是一个死循环,即使别的代码都执行完了,即使别的函数都退出了,这个函数仍然像永不消逝的电波一般,经典常驻.显然,只有死循环才能代码永恒.才能代表忠诚.这是每一个守护者的职责.

usb_stor_control_thread(),其代码如下:

281 static int usb_stor_control_thread(void * __us)
    282 {
    283         struct us_data *us = (struct us_data *)__us;
    284         struct Scsi_Host *host = us->host;
    285
    286         lock_kernel();
    287
    288         /*
    289          * This thread doesn't need any user-level access,
    290          * so get rid of all our resources.
    291          */
    292         daemonize("usb-storage");
    293
    294         current->flags |= PF_NOFREEZE;
    295
    296         unlock_kernel();
    297
    298         /* signal that we've started the thread */
    299         complete(&(us->notify));
    300
    301         for(;;) {
    302                 US_DEBUGP("*** thread sleeping./n");
    303                 if(down_interruptible(&us->sema))
    304                         break;
    305
    306                 US_DEBUGP("*** thread awakened./n");
    307
    308                 /* lock the device pointers */
    309                 down(&(us->dev_semaphore));
    310
    311                 /* if us->srb is NULL, we are being asked to exit */
    312                 if (us->srb == NULL) {
    313                         US_DEBUGP("-- exit command received/n");
    314                         up(&(us->dev_semaphore));
    315                         break;
    316                 }
    317
    318                 /* lock access to the state */
    319                 scsi_lock(host);
    320
    321                 /* has the command timed out *already* ? */
    322                 if (test_bit(US_FLIDX_TIMED_OUT, &us->flags)) {
    323                         us->srb->result = DID_ABORT << 16;
    324                         goto SkipForAbort;
    325                 }
    326
    327                 /* don't do anything if we are disconnecting */
    328                 if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) {
    329                         US_DEBUGP("No command during disconnect/n");
    330                         goto SkipForDisconnect;
    331                 }
    332
    333                 scsi_unlock(host);
    334
    335                 /* reject the command if the direction indicator
    336                  * is UNKNOWN
    337                  */
    338                 if (us->srb->sc_data_direction == DMA_BIDIRECTIONAL) {
    339                         US_DEBUGP("UNKNOWN data direction/n");
    340                         us->srb->result = DID_ERROR << 16;
    341                 }
    342
    343                 /* reject if target != 0 or if LUN is higher than
    344                  * the maximum known LUN
    345                  */
    346                 else if (us->srb->device->id &&
    347                                 !(us->flags & US_FL_SCM_MULT_TARG)) {
    348                         US_DEBUGP("Bad target number (%d:%d)/n",
    349                                   us->srb->device->id, us->srb->device->lun);
    350                         us->srb->result = DID_BAD_TARGET << 16;
    351                 }
    352
    353                 else if (us->srb->device->lun > us->max_lun) {
    354                         US_DEBUGP("Bad LUN (%d:%d)/n",
    355                                   us->srb->device->id, us->srb->device->lun);
    356                         us->srb->result = DID_BAD_TARGET << 16;
    357                 }
    358
    359                 /* Handle those devices which need us to fake
    360                  * their inquiry data */
    361                 else if ((us->srb->cmnd[0] == INQUIRY) &&
    362                             (us->flags & US_FL_FIX_INQUIRY)) {
    363                         unsigned char data_ptr[36] = {
    364                             0x00, 0x80, 0x02, 0x02,
    365                             0x1F, 0x00, 0x00, 0x00};
    366
    367                         US_DEBUGP("Faking INQUIRY command/n");
    368                         fill_inquiry_response(us, data_ptr, 36);
    369                         us->srb->result = SAM_STAT_GOOD;
    370                 }
    371
    372                 /* we've got a command, let's do it! */
    373                 else {
    374                         US_DEBUG(usb_stor_show_command(us->srb));
    375                         us->proto_handler(us->srb, us);
    376                 }
    377
    378                 /* lock access to the state */
    379                 scsi_lock(host);
    380
    381                 /* indicate that the command is done */
    382                 if (us->srb->result != DID_ABORT << 16) {
    383                         US_DEBUGP("scsi cmd done, result=0x%x/n",
    384                                    us->srb->result);
    385                         us->srb->scsi_done(us->srb);
    386                 } else {
    387 SkipForAbort:
    388                         US_DEBUGP("scsi command aborted/n");
    389                 }
    390
    391                 /* If an abort request was received we need to signal that
    392                  * the abort has finished.  The proper test for this is
    393                  * the TIMED_OUT flag, not srb->result == DID_ABORT, because
    394                  * a timeout/abort request might be received after all the
    395                  * USB processing was complete. */
    396                 if (test_bit(US_FLIDX_TIMED_OUT, &us->flags))
    397                         complete(&(us->notify));
    398
    399                 /* finished working on this command */
    400 SkipForDisconnect:
    401                 us->srb = NULL;
    402                 scsi_unlock(host);
    403
    404                 /* unlock the device pointers */
    405                 up(&(us->dev_semaphore));
    406         } /* for (;;) */
    407
    408         /* notify the exit routine that we're actually exiting now
    409          *
    410          * complete()/wait_for_completion() is similar to up()/down(),
    411          * except that complete() is safe in the case where the structure
    412          * is getting deleted in a parallel mode of execution (i.e. just
    413          * after the down() -- that's necessary for the thread-shutdown
    414          * case.
    415          *
    416          * complete_and_exit() goes even further than this -- it is safe in
    417          * the case that the thread of the caller is going away (not just
    418          * the structure) -- this is necessary for the module-remove case.
    419          * This is important in preemption kernels, which transfer the flow
    420          * of execution immediately upon a complete().
    421          */
    422         complete_and_exit(&(us->notify), 0);
    423 }

284,定义了一个Scsi_Host的指针host,令她指向us->host,也就是刚刚用scsi_host_alloc()申请的那个Scsi_Host结构体变量.

292,daemonize("usb-storage"),其实,这句话才是真正创建精灵进程的,daemonize()函数来自内核的核心位置,kernel/exit.c,她完成了这么一件事情,把一个普通的进程转换成为了精灵进程,不过此处咱们可以不去深究精灵进程的原理,甚至咱们可以认为这句话没有做任何事情,只是从此之后咱们ps命令一看能够看到有一个叫做usb-storage的进程.比如下面所看到的:

localhost:~ # ps -el
F S
   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S
     0     1     0  0  76   0 -   195 -      ?        00:00:02 init
1 S
     0     2     1  0 -40   - -     0 migrat ?        00:00:00 migration/0
1 S
     0     3     1  0  94  19 -     0 ksofti ?        00:00:00 ksoftirqd/0
1 S
     0    18     1  0  70  -5 -     0 worker ?        00:00:00 events/0
1 S
     0    26     1  0  71  -5 -     0 worker ?        00:00:00 khelper
1 S
     0    27     1  0  70  -5 -     0 worker ?        00:00:00 kthread
1 S
     0    45    27  0  72  -5 -     0 worker ?        00:00:00 kacpid
1 S
     0   229    27  0  80   0 -     0 pdflus ?        00:00:00 pdflush
1 S
     0   230    27  0  75   0 -     0 pdflus ?        00:00:00 pdflush
1 S
     0   231     1  0  76   0 -     0 kswapd ?        00:00:00 kswapd0
1 S
     0   961    27  0  70  -5 -     0 scsi_e ?        00:00:00 scsi_eh_0
1 S
     0  1033    27  0  70  -5 -     0 scsi_e ?        00:00:00 scsi_eh_1
1 S
     0  1045    27  0  71  -5 -     0 scsi_e ?        00:00:00 scsi_eh_2
1 S
     0  1047    27  0  70  -5 -     0 worker ?        00:00:00 scsi_wq_2
5 S
     0  1262     1  0  72  -4 -  1774 -      ?        00:00:02 udevd
1 S
     0  1939    27  0  70  -5 -     0 hub_th ?        00:00:00 khubd
1 S
     0  7804    27  0  70  -5 -     0 scsi_e ?        00:00:00 scsi_eh_3
1 S
     0  7805    27  0  70  -5 -     0 -      ?        00:00:00 usb-storage
4 S
     0 13905 13902  0  75   0 -  2430 wait   pts/1    00:00:00 bash
0 R
     0 19098 13905  0  77   0 -   821 -      pts/1    00:00:00 ps

显然,您在终端按ctrl-c是不可能中止这个usb-storage进程的.这是精灵进程诸多特性中的一个,她运行于后台.试一下kill -9加进程号,看你能杀死她不?(系统崩溃了我可不负责哦...)

294,这里为目前的进程设置一个flag,PF_NOFREEZE,在整个内核代码中,这个flag也只出现过几次.这个flag是与电源管理相关的,2.6的内核为了实现与Windows相似的一个功能,Hibernate,也就是"冬眠",(别说您不知道,Windows关机选项里面有"关机","重启","注销","Stand by",以及"Hibernate").在内核编译菜单里面,Power managerment options,有一个选项Software Suspend,也就是内核编译选项中的CONFIG_SOFTWARE_SUSPEND,选择了她使得机器可以被suspended.显然咱们不用care.但是这里之所以要设置这个flag,是因为suspend要求把内存里的冬冬写到磁盘上,而一个进程设置了这个flag就表明它在suspend的时候不会被冻住,用行话来讲就是,they’re not refrigerated during a suspend.freeze就是冷冻,冻住的意思,过去分词frozen是形容词,冻结的,冰冻的,其实就是让进程睡眠.所以总的来说,这里的做法就是说,即使系统suspend,这个进程,或者准确地说,这个内核线程,也不应该进入睡眠.

要执行这两个操作需要执行lock_kernel()/unlock_kernel()这一对函数.然后299,执行complete唤醒前面那节的父进程.而子进程并不退出,她继续行走,她无怨无悔的行走,只不过余秋雨先生是为追寻人类文明足迹而进行的域外旅程,而此处子进程()行的她对内核的守护.她像天使一般,守护着心爱的人.

于是咱们也继续跟着她行走,299, complete(&(us->notify)),这正是和刚才在父进程里看到的那句使进程进入睡眠的wait_for_completion(&(us->notify))相对应,这里自然是唤醒父进程,我们先继续看一下子进程,稍候马上去看父进程.

301,一个for语句死循环,尽管外面的世界很精彩,但是咱们去看看for里面的世界也不妨.

303,down_interruptible()函数,事实上302行的注释已经告诉咱们,thread将进入睡眠了...,也许她累了.down_interruptible的参数是&us->sema,不陌生吧,我们之前讲信号量讲互斥锁的时候就已经提过了us->sema.所以这里很简单,就是想获得这把锁,但是别忘了,我们当初就介绍过,这把锁一开始就被初始化为0,也就是说它属于那种指腹为婚的情形,一到这个世界来就告诉别人自己已经是名花有主了.因此,这里只能进入睡眠,等待一个up()函数去释放锁.谁会调用up()函数呢?暂时先不管它,我们先关注一下父进程,毕竟我们自己进入了睡眠,而之前我们把父进程唤醒了.

 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值