• 正文
    • 實(shí)例
  • 相關(guān)推薦
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

手把手教Linux驅(qū)動(dòng)——待隊(duì)列waitq

2020/10/05
345
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

本文主要講解如何通過(guò)等待隊(duì)列實(shí)現(xiàn)對(duì)進(jìn)程的阻塞。

應(yīng)用場(chǎng)景:

當(dāng)進(jìn)程要獲取某些資源(例如從網(wǎng)卡讀取數(shù)據(jù))的時(shí)候,但資源并沒(méi)有準(zhǔn)備好(例如網(wǎng)卡還沒(méi)接收到數(shù)據(jù)),這時(shí)候內(nèi)核必須切換到其他進(jìn)程運(yùn)行,直到資源準(zhǔn)備好再喚醒進(jìn)程。

waitqueue (等待隊(duì)列)就是內(nèi)核用于管理等待資源的進(jìn)程,當(dāng)某個(gè)進(jìn)程獲取的資源沒(méi)有準(zhǔn)備好的時(shí)候,可以通過(guò)調(diào)用 add_wait_queue()函數(shù)把進(jìn)程添加到 waitqueue 中,然后切換到其他進(jìn)程繼續(xù)執(zhí)行。當(dāng)資源準(zhǔn)備好,由資源提供方通過(guò)調(diào)用 wake_up()函數(shù)來(lái)喚醒等待的進(jìn)程。

定義頭文件:

#include

定義和初始化等待隊(duì)列頭(workqueue):

靜態(tài)的,用宏:

#define DECLARE_WAIT_QUEUE_HEAD(name) 
    wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

動(dòng)態(tài)的,也是用宏:

#define init_waitqueue_head(q)              
    do {                        
        static struct lock_class_key __key; 
                            
        __init_waitqueue_head((q), #q, &__key); 
    } while (0)

定義實(shí)例

wait_queue_head_t wq;
init_waitqueue_head(&wq);

阻塞接口:

wait_event(wq, condition)
wait_event_timeout(wq, condition, timeout)
wait_event_interruptible(wq, condition)
wait_event_interruptible_timeout(wq, condition, timeout)
wait_event_hrtimeout(wq, condition, timeout)
wait_event_interruptible_hrtimeout(wq, condition, timeout)
wait_event_interruptible_exclusive(wq, condition)
wait_event_interruptible_locked(wq, condition)
wait_event_interruptible_locked_irq(wq, condition)
wait_event_interruptible_exclusive_locked(wq, condition)
wait_event_interruptible_exclusive_locked_irq(wq, condition)
wait_event_killable(wq, condition)
wait_event_lock_irq_cmd(wq, condition, lock, cmd)
wait_event_lock_irq(wq, condition, lock)
wait_event_interruptible_lock_irq_cmd(wq, condition, lock, cmd)
wait_event_interruptible_lock_irq(wq, condition, lock)
wait_event_interruptible_lock_irq_timeout(wq, condition, lock,  timeout)

參數(shù)

wq        定義的等待隊(duì)列頭,
condition 為條件表達(dá)式,當(dāng) wake up 后,condition 為真時(shí),喚醒阻塞的進(jìn)程,為假時(shí),繼續(xù)睡眠。

功能說(shuō)明

接口版本比較多,各自都有自己合適的應(yīng)用場(chǎng)合,但是常用的是前面四個(gè)。

wait_event:不可中斷的睡眠,條件一直不滿(mǎn)足,會(huì)一直睡眠。
wait_event_timeout:不可中斷睡眠,當(dāng)超過(guò)指定的 timeout(單位是 jiffies)時(shí)間,不管有沒(méi)有 wake up,還是條件沒(méi)滿(mǎn)足,都要喚醒進(jìn)程,此時(shí)返回的是 0。在 timeout 時(shí)間內(nèi)條件滿(mǎn)足返回值為 timeout 或者 1;
wait_event_interruptible:可被信號(hào)中斷的睡眠,被信號(hào)打斷喚醒時(shí),返回負(fù)值 -ERESTARTSYS;wake up 時(shí),條件滿(mǎn)足的,返回 0。除了 wait_event 沒(méi)有返回值,其它的都有返回,有返回值的一般都要判斷返回值。如下例:

    int flag = 0;
    if(wait_event_interruptible(&wq,flag == 1))
        return -ERESTARTSYS;

wait_event_interruptible_timeout:是 wait_event_timeout 和 wait_event_interruptible_timeout 的結(jié)合版本,有它們兩個(gè)的特點(diǎn)。

其他的接口,用的不多,有興趣可以自己看看。

解除阻塞接口(喚醒)

接口定義:

#define wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_nr(x, nr)       __wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x)          __wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_locked(x)       __wake_up_locked((x), TASK_NORMAL, 1)
#define wake_up_all_locked(x)       __wake_up_locked((x), TASK_NORMAL, 0)

#define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x)    __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible_sync(x)   __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)

功能說(shuō)明

wake_up:一次只能喚醒掛在這個(gè)等待隊(duì)列頭上的一個(gè)進(jìn)程
wake_up_nr:一次喚起 nr 個(gè)進(jìn)程(等待在同一個(gè) wait_queue_head_t 有很多個(gè))
wake_up_all:一次喚起所有等待在同一個(gè) wait_queue_head_t 上所有進(jìn)程
wake_up_interruptible:對(duì)應(yīng) wait_event_interruptible 版本的 wake up
wake_up_interruptible_sync:保證 wake up 的動(dòng)作原子性,wake_up 這個(gè)函數(shù),很有可能函數(shù)還沒(méi)執(zhí)行完,就被喚起來(lái)進(jìn)程給搶占了,這個(gè)函數(shù)能夠保證 wak up 動(dòng)作完整的執(zhí)行完成。

其他的也是與對(duì)應(yīng)阻塞接口對(duì)應(yīng)的。

使用實(shí)例

以字符設(shè)備為例,在沒(méi)有數(shù)據(jù)的時(shí)候,在 read 函數(shù)中實(shí)現(xiàn)讀阻塞,當(dāng)向內(nèi)核寫(xiě)入數(shù)據(jù)時(shí),則喚醒阻塞在該等待隊(duì)列的所有任務(wù)。

讀操作

static ssize_t hello_read(struct file *filp,char __user *buf,size_t size,loff_t *poss)  
{  
    wait_event_interruptible(rwq,flage!=0);
    ……………
    flage=0;
    wake_up_interruptible(&wwq);
    return size;  
}   

寫(xiě)操作

static ssize_t hello_write(struct file *filp,const char __user *buf,size_t size,loff_t *poss)  
{  
wait_event_interruptible(wwq,flage!=1);
    ……………
flage=1;
wake_up_interruptible(&rwq);
    return size;  
}

如何同步支持非阻塞?

上述操作雖然實(shí)現(xiàn)了阻塞功能,但是我們?cè)?a class="article-link" target="_blank" href="/tag/%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F/">應(yīng)用程序打開(kāi)一個(gè)字符設(shè)備的時(shí)候,有時(shí)候我們希望操作是非阻塞的,比如:

fd=open("/dev/hello",O_RDONLY|O_NONBLOCK); 

那么驅(qū)動(dòng)如何得到這個(gè)標(biāo)記呢?

參考《手把手教 Linux 驅(qū)動(dòng) 6-inode,file,file_operations 關(guān)系》,該標(biāo)記會(huì)存儲(chǔ)在結(jié)構(gòu)體 struct file 的 f_flags 成員中。

所以程序可以修改如下:

static ssize_t hello_read(struct file *filp,char __user *buf,size_t size,loff_t *poss)  
{  
	int ret = 0;

	if(flage==0)
	{
		if(filp->f_flags & O_NONBLOCK)
		{
			
		  return -EAGAIN;
		}
		wait_event_interruptible(rwq,flage!=0);
	}	
        ……………
	flage=0;
	wake_up_interruptible(&wwq);
    return size;  
}

一種靈活的添加刪除等待隊(duì)列頭中的等待隊(duì)列:

(1)定義:
靜態(tài):

#define DECLARE_WAITQUEUE(name, tsk)                    
    wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

(2)動(dòng)態(tài):

wait_queue_t wa;
init_waitqueue_entry(&wa,&tsk);

tsk 是進(jìn)程結(jié)構(gòu)體,一般是 current(linux 當(dāng)前進(jìn)程就是用這個(gè)獲取)。還可以用下面的,設(shè)置自定義的等待隊(duì)列回調(diào)函數(shù),上面的是 linux 默認(rèn)的一個(gè)回調(diào)函數(shù) default_wake_function(),不過(guò)默認(rèn)的用得最多:

wait_queue_t wa;
wa->private = &tsk;
int func(wait_queue_t *wait, unsigned mode, int flags, void *key)
{
    //
}
init_waitqueue_func_entry(&wa,func);

(回調(diào)有什么作用?)
用下面函數(shù)將等待隊(duì)列,加入到等待隊(duì)列頭(帶 remove 的是從工作隊(duì)列頭中刪除工作隊(duì)列):

extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
extern void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);
extern void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

上面的阻塞和解除阻塞接口,只能是對(duì)當(dāng)前進(jìn)程阻塞 / 解除阻塞,有了這幾個(gè)靈活的接口,我們可以單獨(dú)定義一個(gè)等待隊(duì)列,只要獲取進(jìn)程 task_struct 指針,我們可以將任何進(jìn)程加入到這個(gè)等待隊(duì)列,然后加入到等待隊(duì)列頭,我們能將其它任何進(jìn)程(不僅僅是當(dāng)前進(jìn)程),掛起睡眠,當(dāng)然喚醒時(shí),如果用 wake_up_all 版本的話(huà),也會(huì)一同喚起。這種情況,阻塞不能用上面的接口了,我們需要用下一節(jié)講述的接口(schedule()),解除阻塞可以用 wake_up,wake_up_interruptible 等。

更高級(jí)靈活的阻塞:

阻塞當(dāng)前進(jìn)程的原理:用函數(shù) set_current_state()修改當(dāng)前進(jìn)程為 TASK_INTERRUPTIBLE(不可中斷睡眠)或 TASK_UNINTERRUPTIBLE(可中斷睡眠)狀態(tài),然后調(diào)用 schedule()告訴內(nèi)核重新調(diào)度,由于當(dāng)前進(jìn)程狀態(tài)已經(jīng)為睡眠狀態(tài),自然就不會(huì)被調(diào)度。schedule()簡(jiǎn)單說(shuō)就是告訴內(nèi)核當(dāng)前進(jìn)程主動(dòng)放棄 CPU 控制權(quán)。這樣來(lái),就可以說(shuō)當(dāng)前進(jìn)程在此處睡眠,即阻塞在這里。

在上一小節(jié)“靈活的添加刪等待隊(duì)列頭中的等待隊(duì)列”,將任意進(jìn)程加入到 waitqueue,然后類(lèi)似用:

task_struct *tsk;
wait_queue_t wa;
// 假設(shè) tsk 已經(jīng)指向某進(jìn)程控制塊
p->state = TASK_INTERRUPTIBLE;//or TASK_UNINTERRUPTIBLE
init_waitqueue_entry(&wa,&tsk);

就能將任意進(jìn)程掛起,當(dāng)然,還需要將 wa,掛到等待隊(duì)列頭,然后用 wait_event(&wa),進(jìn)程就會(huì)從就緒隊(duì)列中退出,進(jìn)入到睡眠隊(duì)列,直到 wake up 時(shí),被掛起的進(jìn)程狀態(tài)被修改為 TASK_RUNNING,才會(huì)被再次調(diào)度。(主要是 schedule()下面會(huì)說(shuō)到)。

wait_event 實(shí)現(xiàn)原理:

先看下 wait_event 實(shí)現(xiàn):

#define __wait_event(wq, condition)                     
do {                                    
    DEFINE_WAIT(__wait);                        
                                    
    for (;;) {                          
        prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    
        if (condition)                      
            break;                      
        schedule();                     
    }                               
    finish_wait(&wq, &__wait);                  
} while (0)

#define wait_event(wq, condition)                   
do {                                    
    if (condition)                          
        break;                          
    __wait_event(wq, condition);                    
} while (0)

DEFINE_WAIT:

定義一個(gè)工作隊(duì)列。

prepare_to_wait:

定義:void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
功能:將工作隊(duì)列 wait 加入到工作隊(duì)列頭 q,并將當(dāng)前進(jìn)程設(shè)置為 state 指定的狀態(tài),一般是 TASK_UNINTERRUPTIBLE 或 TASK_INTERRUPTIBLE 狀態(tài)(在這函數(shù)里有調(diào)用 set_current_state)。
    第一個(gè)參數(shù):工作隊(duì)列頭
    第二個(gè)參數(shù):工作隊(duì)列
    第三個(gè)參數(shù):當(dāng)前進(jìn)程要設(shè)置的狀態(tài)

finish_wait:

用了 prepare_to_wait 之后,當(dāng)退出時(shí),一定要用這個(gè)函數(shù)清空等待隊(duì)列。

功能:

該函數(shù)首先調(diào)用 prepare_to_wait,修改進(jìn)程到睡眠狀態(tài),

條件不滿(mǎn)足,schedule()就放棄 CPU 控制權(quán),睡眠,

當(dāng) wakeup 的時(shí)候,阻塞在 wq(也可以說(shuō)阻塞在 wait_event 處)等待隊(duì)列頭上的進(jìn)程,再次得到運(yùn)行,接著執(zhí)行 schedule()后面的代碼,這里,顯然是個(gè)循環(huán),prepare_to_wait 再次設(shè)置當(dāng)前進(jìn)程為睡眠狀態(tài),然后判斷條件是否滿(mǎn)足,

滿(mǎn)足就退出循環(huán),finish_wait 將當(dāng)前進(jìn)程恢復(fù)到 TASK_RUNNING 狀態(tài),也就意味著阻塞解除。不滿(mǎn)足,繼續(xù)睡下去。如此反復(fù)等待條件成立。

明白這個(gè)過(guò)程,用 prepare_to_wait 和 schedule()來(lái)實(shí)現(xiàn)更為靈活的阻塞,就很簡(jiǎn)單了,解除阻塞和前面的一樣用 wake_up,wake_up_interruptible 等。

下面是 wake_up 和 wait_event 流程圖:

wait_event 和 wake_up 流程

獨(dú)占等待

當(dāng)調(diào)用 wake_up 時(shí),所有等待在該隊(duì)列上的進(jìn)程都被喚醒,并進(jìn)入可運(yùn)行狀態(tài)如果只有一個(gè)進(jìn)程可獲得資源,此時(shí),其他的進(jìn)程又將再次進(jìn)入休眠,如果數(shù)量很大,被稱(chēng)為”瘋狂售群”。這樣會(huì)非常占用系統(tǒng)資源。

解決方法:

wait_queue_t 成員 flage 有個(gè)重要的標(biāo)志 WQ_FLAG_EXCLUSIVE,表示:

當(dāng)一個(gè)等待隊(duì)列入口有 WQ_FLAG_EXCLUSEVE 標(biāo)志置位, 它被添加到等待隊(duì)列的尾部 . 沒(méi)有這個(gè)標(biāo)志的入口項(xiàng), 添加到開(kāi)始 .
當(dāng) wake_up 被在一個(gè)等待隊(duì)列上調(diào)用, 它在喚醒第一個(gè)有 WQ_FLAG_EXCLUSIVE 標(biāo)志的進(jìn)程后停止 .

wait_event 默認(rèn)總是將 waitqueue 加入開(kāi)始,而 wake_up 時(shí)總是一個(gè)一個(gè)的從開(kāi)始處喚醒,如果不斷有 waitqueue 加入,那么最開(kāi)始加入的,就一直得不到喚醒,有這個(gè)標(biāo)志,就避免了這種情況。

prepare_to_wait_exclusive()就是加入了這個(gè)標(biāo)志的。

補(bǔ)充

Linux 將進(jìn)程狀態(tài)描述為如下五種:

1. TASK_RUNNING:可運(yùn)行狀態(tài)。處于該狀態(tài)的進(jìn)程可以被調(diào)度執(zhí)行而成為當(dāng)前進(jìn)程。

2. TASK_INTERRUPTIBLE:可中斷的睡眠狀態(tài)。處于該狀態(tài)的進(jìn)程在所需資源有效時(shí)被喚醒,也可以通過(guò)信號(hào)或定時(shí)中斷喚醒(因?yàn)橛?signal_pending()函數(shù))。

3. TASK_UNINTERRUPTIBLE:不可中斷的睡眠狀態(tài)。處于該狀態(tài)的進(jìn)程僅當(dāng)所需資源有效時(shí)被喚醒。

4. TASK_ZOMBIE:僵尸狀態(tài)。表示進(jìn)程結(jié)束且已釋放資源,但其 task_struct 仍未釋放。

5. TASK_STOPPED:暫停狀態(tài)。處于該狀態(tài)的進(jìn)程通過(guò)其他進(jìn)程的信號(hào)才能被喚醒

Linux 通過(guò)結(jié)構(gòu)體 task_struct 維護(hù)所有運(yùn)行的線(xiàn)程、進(jìn)程,不同狀態(tài)的任務(wù),會(huì)由不同的隊(duì)列進(jìn)行維護(hù),schedule()函數(shù)就負(fù)責(zé)根據(jù)這些狀態(tài)的變化調(diào)度這些任務(wù)。關(guān)于進(jìn)程的調(diào)度,后續(xù)會(huì)新開(kāi)文章詳細(xì)介紹。

實(shí)例

下面實(shí)例主要功能是基于我們之前課程《手把手教 Linux 驅(qū)動(dòng) 3- 之字符設(shè)備架構(gòu)詳解,有這篇就夠了》最后的代碼實(shí)例,在此基礎(chǔ)上增加寫(xiě)阻塞的功能。

內(nèi)核中有緩沖內(nèi)存,以及是否可以訪(fǎng)問(wèn)的標(biāo)記;

int flage=0;  //1:有數(shù)據(jù)可讀  0:無(wú)數(shù)據(jù),不可讀
char kbuf[128];

初始狀態(tài)下 flage 為 0,沒(méi)有數(shù)據(jù);

應(yīng)用進(jìn)程讀取數(shù)據(jù)會(huì)調(diào)用到內(nèi)核函數(shù) hello_read(),如果 flage 為 1,則直接讀走數(shù)據(jù),并將改 flage 置 1,如果 flage 為 0,則進(jìn)程阻塞,直到有進(jìn)程寫(xiě)入數(shù)據(jù)將該 flage 置 1;

應(yīng)用進(jìn)程每次寫(xiě)入數(shù)據(jù)會(huì)調(diào)用到內(nèi)核函數(shù) hello_write(),如果 flage 為 0,則直接寫(xiě)入數(shù)據(jù),并設(shè)置 flage 為 1,如果為 1,則阻塞,直到有其他進(jìn)程調(diào)用到讀函數(shù) hello_read()將 flage 置 0。

驅(qū)動(dòng)

/********************************************* *hellodev.c *********************************************/  #include   #include   #include  #include  #include  #include   #include   #include   #include   #include   #include   #include   #include   #include   #include    #include   static int hello_major = 250;  static struct class *hello_class;#define  DEV_NAME "hello_cls" module_param(hello_major,int,S_IRUGO);  dev_t devno; struct cdev cdev;  int num;int flage=0;char kbuf[128];wait_queue_head_t rwq; //read wqwait_queue_head_t wwq;  //write wqint hello_open(struct inode *inode,struct file *filp)  {     return 0;  }  int hello_release(struct inode *inode,struct file *filp)  {      return 0;  }  static ssize_t hello_read(struct file *filp,char __user *buf,size_t size,loff_t *poss)  {    int ret = 0;   if(flage==0)  {    if(filp->f_flags & O_NONBLOCK)    {            return -EAGAIN;    }    wait_event_interruptible(rwq,flage!=0);  }    if(copy_to_user(buf,kbuf,size))   {    return -EFAULT;  }  flage=0;  wake_up_interruptible(&wwq);    return size;  }   static ssize_t hello_write(struct file *filp,const char __user *buf,size_t size,loff_t *poss)  {      int ret = 0;  if(size>128||size<0)  {    return -EINVAL;  }  wait_event_interruptible(wwq,flage!=1);    if(copy_from_user(kbuf,buf,size))   {    return -EFAULT;  }  flage=1;  wake_up_interruptible(&rwq);    return size;  }   static const struct file_operations hello_fops =  {      .owner  = THIS_MODULE,      .read   = hello_read,      .write  = hello_write,      .open   = hello_open,      .release = hello_release,  };   static int hellodev_init(void)  {    int result;    int i;    struct device *hello_dev;   devno = MKDEV(hello_major,0);    if(hello_major)        result = register_chrdev_region(devno,2,"hello");    else      {        result = alloc_chrdev_region(&devno,0,2,"hello");        hello_major = MAJOR(devno);    }    if(result < 0)        return result;       cdev_init(&cdev,&hello_fops);    cdev.owner = THIS_MODULE;    cdev.ops = &hello_fops;     cdev_add(&cdev,MKDEV(hello_major,0),1);  init_waitqueue_head(&rwq);  init_waitqueue_head(&wwq);   hello_class = class_create(THIS_MODULE, DEV_NAME);// 類(lèi)名字   if (IS_ERR(hello_class)) {    printk(KERN_WARNING "class_create faihello ! n");    goto err_3;  }   hello_dev = device_create(hello_class, NULL, devno, NULL, "hello");  if (IS_ERR(hello_dev)) {    printk(KERN_WARNING "device_create faihello! n");    goto err_4;}return0; err_4:  class_destroy(hello_class);err_3:  cdev_del(&cdev);  unregister_chrdev_region(MKDEV(hello_major,0),2);     return 0;  }  static void hellodev_exit(void)  {   device_destroy(hello_class, devno);    class_destroy(hello_class);cdev_del(&cdev);  unregister_chrdev_region(MKDEV(hello_major,0),2);  }    MODULE_LICENSE("GPL");MODULE_DESCRIPTION("yikoulinux");module_init(hellodev_init);  module_exit(hellodev_exit);

測(cè)試程序

read.c

#include #include #include #include #include #include  int main(){  int fd= 0;  char buf[128];  int num;//  fd=open("/dev/hello",O_RDONLY); 阻塞方式讀取 fd=open("/dev/hello",O_RDONLY|O_NONBLOCK);// 非阻塞  if(fd<0)   {         printf("open memdev failed!n");         return -1;                 }       read(fd,buf,sizeof(buf));      printf("num:%sn",buf);    close(fd);  return 0;       }

write.c

#include #include #include #include #include #include int main(){  int fd =0;  char buf[128]="hello yikouLlinux";  int num;   fd=open("/dev/hello",O_RDWR);       if(fd <0)       {         printf("open device failed!n");         return -1;               }write(fd,buf,sizeof(buf));  close(fd);  return 0;       }

掌握了等待隊(duì)列的用法,后面我們就可以進(jìn)行中斷的講解了。

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計(jì)資源下載
  • 產(chǎn)業(yè)鏈客戶(hù)資源
  • 寫(xiě)文章/發(fā)需求
立即登錄

公眾號(hào)『一口Linux』號(hào)主彭老師,擁有15年嵌入式開(kāi)發(fā)經(jīng)驗(yàn)和培訓(xùn)經(jīng)驗(yàn)。曾任職ZTE,某研究所,華清遠(yuǎn)見(jiàn)教學(xué)總監(jiān)。擁有多篇網(wǎng)絡(luò)協(xié)議相關(guān)專(zhuān)利和軟件著作。精通計(jì)算機(jī)網(wǎng)絡(luò)、Linux系統(tǒng)編程、ARM、Linux驅(qū)動(dòng)、龍芯、物聯(lián)網(wǎng)。原創(chuàng)內(nèi)容基本從實(shí)際項(xiàng)目出發(fā),保持原理+實(shí)踐風(fēng)格,適合Linux驅(qū)動(dòng)新手入門(mén)和技術(shù)進(jìn)階。