溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Linux系統(tǒng)驅(qū)動(dòng)開(kāi)發(fā)的知識(shí)點(diǎn)有哪些

發(fā)布時(shí)間:2022-01-26 16:47:39 來(lái)源:億速云 閱讀:117 作者:柒染 欄目:開(kāi)發(fā)技術(shù)

Linux系統(tǒng)驅(qū)動(dòng)開(kāi)發(fā)的知識(shí)點(diǎn)有哪些,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

文件私有數(shù)據(jù)

大多數(shù)linux的驅(qū)動(dòng)工程師都將文件私有數(shù)據(jù)private_data指向設(shè)備結(jié)構(gòu)體,read等個(gè)函數(shù)通過(guò)調(diào)用private_data來(lái)訪問(wèn)設(shè)備結(jié)構(gòu)體。這樣做的目的是為了區(qū)分子設(shè)備,如果一個(gè)驅(qū)動(dòng)有兩個(gè)子設(shè)備(次設(shè)備號(hào)分別為0和1),那么使用private_data就很方便。 這里有一個(gè)函數(shù)要提出來(lái):

 container_of(ptr,type,member)//通過(guò)結(jié)構(gòu)體成員的指針找到對(duì)應(yīng)結(jié)構(gòu)體的的指針
 1

其定義如下:

 /**
 *container_of-castamemberofastructureouttothecontainingstructure
 *@ptr:    thepointertothemember.
 *@type:    thetypeofthecontainerstructthisisembeddedin.
 *@member:    thenameofthememberwithinthestruct.
 *
 */
 #define container_of(ptr,type,member)({            \
     const typeof(((type*)0)->member)*__mptr=(ptr);    \
     (type*)((char*)__mptr-offsetof(type,member));})

字符設(shè)備驅(qū)動(dòng)的結(jié)構(gòu)

可以概括如下圖: Linux系統(tǒng)驅(qū)動(dòng)開(kāi)發(fā)的知識(shí)點(diǎn)有哪些 字符設(shè)備是3大類(lèi)設(shè)備(字符設(shè)備、塊設(shè)備、網(wǎng)絡(luò)設(shè)備)中較簡(jiǎn)單的一類(lèi)設(shè)備,其驅(qū)動(dòng)程序中完成的主要工作是初始化、添加和刪除cdev結(jié)構(gòu)體,申請(qǐng)和釋放設(shè)備號(hào),以及填充file_operation結(jié)構(gòu)體中操作函數(shù),并實(shí)現(xiàn)file_operations結(jié)構(gòu)體中的read()、write()、ioctl()等重要函數(shù)。如圖所示為cdev結(jié)構(gòu)體、file_operations和用戶(hù)空間調(diào)用驅(qū)動(dòng)的關(guān)系。

自旋鎖與信號(hào)量

為了避免并發(fā),防止競(jìng)爭(zhēng)。內(nèi)核提供了一組同步方法來(lái)提供對(duì)共享數(shù)據(jù)的保護(hù)。我們的重點(diǎn)不是介紹這些方法的詳細(xì)用法,而是強(qiáng)調(diào)為什么使用這些方法和它們之間的差別。 Linux使用的同步機(jī)制可以說(shuō)從2.0到2.6以來(lái)不斷發(fā)展完善。從最初的原子操作,到后來(lái)的信號(hào)量,從大內(nèi)核鎖到今天的自旋鎖。這些同步機(jī)制的發(fā)展伴隨Linux從單處理器到對(duì)稱(chēng)多處理器的過(guò)度;伴隨著從非搶占內(nèi)核到搶占內(nèi)核的過(guò)度。鎖機(jī)制越來(lái)越有效,也越來(lái)越復(fù)雜。目前來(lái)說(shuō)內(nèi)核中原子操作多用來(lái)做計(jì)數(shù)使用,其它情況最常用的是兩種鎖以及它們的變種:一個(gè)是自旋鎖,另一個(gè)是信號(hào)量。

自旋鎖 自旋鎖是專(zhuān)為防止多處理器并發(fā)而引入的一種鎖,它在內(nèi)核中大量應(yīng)用于中斷處理等部分(對(duì)于單處理器來(lái)說(shuō),防止中斷處理中的并發(fā)可簡(jiǎn)單采用關(guān)閉中斷的方式,不需要自旋鎖)。 自旋鎖最多只能被一個(gè)內(nèi)核任務(wù)持有,如果一個(gè)內(nèi)核任務(wù)試圖請(qǐng)求一個(gè)已被爭(zhēng)用(已經(jīng)被持有)的自旋鎖,那么這個(gè)任務(wù)就會(huì)一直進(jìn)行忙循環(huán)——旋轉(zhuǎn)——等待鎖重新可用。要是鎖未被爭(zhēng)用,請(qǐng)求它的內(nèi)核任務(wù)便能立刻得到它并且繼續(xù)進(jìn)行。自旋鎖可以在任何時(shí)刻防止多于一個(gè)的內(nèi)核任務(wù)同時(shí)進(jìn)入臨界區(qū),因此這種鎖可有效地避免多處理器上并發(fā)運(yùn)行的內(nèi)核任務(wù)競(jìng)爭(zhēng)共享資源。

 自旋鎖的基本形式如下:
 spin_lock(&mr_lock);
 //臨界區(qū)
 spin_unlock(&mr_lock);1234

信號(hào)量 Linux中的信號(hào)量是一種睡眠鎖。如果有一個(gè)任務(wù)試圖獲得一個(gè)已被持有的信號(hào)量時(shí),信號(hào)量會(huì)將其推入等待隊(duì)列,然后讓其睡眠。這時(shí)處理器獲得自由去執(zhí)行其它代碼。當(dāng)持有信號(hào)量的進(jìn)程將信號(hào)量釋放后,在等待隊(duì)列中的一個(gè)任務(wù)將被喚醒,從而便可以獲得這個(gè)信號(hào)量。 信號(hào)量的睡眠特性,使得信號(hào)量適用于鎖會(huì)被長(zhǎng)時(shí)間持有的情況;只能在進(jìn)程上下文中使用,因?yàn)橹袛嗌舷挛闹惺遣荒鼙徽{(diào)度的;另外當(dāng)代碼持有信號(hào)量時(shí),不可以再持有自旋鎖。 信號(hào)量基本使用形式為:

 static DECLARE_MUTEX(mr_sem);//聲明互斥信號(hào)量
 if(down_interruptible(&mr_sem))
 //可被中斷的睡眠,當(dāng)信號(hào)來(lái)到,睡眠的任務(wù)被喚醒
 //臨界區(qū)
 up(&mr_sem);12345

信號(hào)量和自旋鎖區(qū)別 從嚴(yán)格意義上說(shuō),信號(hào)量和自旋鎖屬于不同層次的互斥手段,前者的實(shí)現(xiàn)有賴(lài)于后者,在信號(hào)量本身的實(shí)現(xiàn)上,為了保證信號(hào)量結(jié)構(gòu)存取的原子性,在多CPU中需要自旋鎖來(lái)互斥。 信號(hào)量是進(jìn)程級(jí)的。用于多個(gè)進(jìn)程之間對(duì)資源的互斥,雖然也是在內(nèi)核中,但是該內(nèi)核執(zhí)行路徑是以進(jìn)程的身份,代表進(jìn)程來(lái)爭(zhēng)奪進(jìn)程。鑒于進(jìn)程上下文切換的開(kāi)銷(xiāo)也很大,因此,只有當(dāng)進(jìn)程占用資源時(shí)間比較長(zhǎng)時(shí),用信號(hào)量才是較好的選擇。 當(dāng)所要保護(hù)的臨界區(qū)訪問(wèn)時(shí)間比較短時(shí),用自旋鎖是非常方便的,因?yàn)樗?jié)省上下文切換的時(shí)間,但是CPU得不到自旋鎖會(huì)在那里空轉(zhuǎn)直到執(zhí)行單元鎖為止,所以要求鎖不能在臨界區(qū)里長(zhǎng)時(shí)間停留,否則會(huì)降低系統(tǒng)的效率 由此,可以總結(jié)出自旋鎖和信號(hào)量選用的3個(gè)原則: 1:當(dāng)鎖不能獲取到時(shí),使用信號(hào)量的開(kāi)銷(xiāo)就是進(jìn)程上線文切換的時(shí)間Tc,使用自旋鎖的開(kāi)銷(xiāo)就是等待自旋鎖(由臨界區(qū)執(zhí)行的時(shí)間決定)Ts,如果Ts比較小時(shí),應(yīng)使用自旋鎖比較好,如果Ts比較大,應(yīng)使用信號(hào)量。 2:信號(hào)量所保護(hù)的臨界區(qū)可包含可能引起阻塞的代碼,而自旋鎖絕對(duì)要避免用來(lái)保護(hù)包含這樣的代碼的臨界區(qū),因?yàn)樽枞馕吨M(jìn)行進(jìn)程間的切換,如果進(jìn)程被切換出去后,另一個(gè)進(jìn)程企圖獲取本自旋鎖,死鎖就會(huì)發(fā)生。 3:信號(hào)量存在于進(jìn)程上下文,因此,如果被保護(hù)的共享資源需要在中斷或軟中斷情況下使用,則在信號(hào)量和自旋鎖之間只能選擇自旋鎖,當(dāng)然,如果一定要是要那個(gè)信號(hào)量,則只能通過(guò)down_trylock()方式進(jìn)行,不能獲得就立即返回以避免阻塞 自旋鎖VS信號(hào)量 需求建議的加鎖方法 低開(kāi)銷(xiāo)加鎖優(yōu)先使用自旋鎖 短期鎖定優(yōu)先使用自旋鎖 長(zhǎng)期加鎖優(yōu)先使用信號(hào)量 中斷上下文中加鎖使用自旋鎖 持有鎖是需要睡眠、調(diào)度使用信號(hào)量

阻塞與非阻塞I/O

一個(gè)驅(qū)動(dòng)當(dāng)它無(wú)法立刻滿(mǎn)足請(qǐng)求應(yīng)當(dāng)如何響應(yīng)?一個(gè)對(duì) read 的調(diào)用可能當(dāng)沒(méi)有數(shù)據(jù)時(shí)到來(lái),而以后會(huì)期待更多的數(shù)據(jù);或者一個(gè)進(jìn)程可能試圖寫(xiě),但是你的設(shè)備沒(méi)有準(zhǔn)備好接受數(shù)據(jù),因?yàn)槟愕妮敵鼍彌_滿(mǎn)了。調(diào)用進(jìn)程往往不關(guān)心這種問(wèn)題,程序員只希望調(diào)用 read 或 write 并且使調(diào)用返回,在必要的工作已完成后,你的驅(qū)動(dòng)應(yīng)當(dāng)(缺省地)阻塞進(jìn)程,使它進(jìn)入睡眠直到請(qǐng)求可繼續(xù)。 阻塞操作是指在執(zhí)行設(shè)備操作時(shí)若不能獲得資源則掛起進(jìn)程,直到滿(mǎn)足可操作的條件后再進(jìn)行操作。 一個(gè)典型的能同時(shí)處理阻塞與非阻塞的globalfifo讀函數(shù)如下:

 /*globalfifo讀函數(shù)*/
 static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count,
     loff_t *ppos)
 {
     int ret;
     struct globalfifo_dev *dev = filp->private_data;
     DECLARE_WAITQUEUE(wait, current);
 
     down(&dev->sem); /* 獲得信號(hào)量 */
     add_wait_queue(&dev->r_wait, &wait); /* 進(jìn)入讀等待隊(duì)列頭 */
 
     /* 等待FIFO非空 */
     if (dev->current_len == 0) {
         if (filp->f_flags &O_NONBLOCK) {
             ret = - EAGAIN;
             goto out;
         }
         __set_current_state(TASK_INTERRUPTIBLE); /* 改變進(jìn)程狀態(tài)為睡眠 */
         up(&dev->sem);
 
         schedule(); /* 調(diào)度其他進(jìn)程執(zhí)行 */
         if (signal_pending(current)) {
             /* 如果是因?yàn)樾盘?hào)喚醒 */
             ret = - ERESTARTSYS;
             goto out2;
         }
 
         down(&dev->sem);
     }
 
     /* 拷貝到用戶(hù)空間 */
     if (count > dev->current_len)
         count = dev->current_len;
 
     if (copy_to_user(buf, dev->mem, count)) {
         ret = - EFAULT;
         goto out;
     } else {
         memcpy(dev->mem, dev->mem + count, dev->current_len - count); /* fifo數(shù)據(jù)前移 */
         dev->current_len -= count; /* 有效數(shù)據(jù)長(zhǎng)度減少 */
         printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count, dev->current_len);
 
         wake_up_interruptible(&dev->w_wait); /* 喚醒寫(xiě)等待隊(duì)列 */
 
         ret = count;
     }
 out:
     up(&dev->sem); /* 釋放信號(hào)量 */
 out2:
     remove_wait_queue(&dev->w_wait, &wait); /* 從附屬的等待隊(duì)列頭移除 */
     set_current_state(TASK_RUNNING);
     return ret;
 }
 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354

 poll方法

使用非阻塞I/O的應(yīng)用程序通常會(huì)使用select()和poll()系統(tǒng)調(diào)用查詢(xún)是否可對(duì)設(shè)備進(jìn)行無(wú)阻塞的訪問(wèn)。select()和poll()系統(tǒng)調(diào)用最終會(huì)引發(fā)設(shè)備驅(qū)動(dòng)中的poll()函數(shù)被執(zhí)行。 這個(gè)方法由下列的原型:

 unsigned int (*poll) (struct file *filp, poll_table *wait);
 1

這個(gè)驅(qū)動(dòng)方法被調(diào)用, 無(wú)論何時(shí)用戶(hù)空間程序進(jìn)行一個(gè) poll, select, 或者 epoll 系統(tǒng)調(diào)用, 涉及一個(gè)和驅(qū)動(dòng)相關(guān)的文件描述符. 這個(gè)設(shè)備方法負(fù)責(zé)這 2 步:

  1. 對(duì)可能引起設(shè)備文件狀態(tài)變化的等待隊(duì)列,調(diào)用poll_wait()函數(shù),將對(duì)應(yīng)的等待隊(duì)列頭添加到poll_table.

  2. 返回一個(gè)位掩碼, 描述可能不必阻塞就立刻進(jìn)行的操作.

poll_table結(jié)構(gòu), 給 poll 方法的第 2 個(gè)參數(shù), 在內(nèi)核中用來(lái)實(shí)現(xiàn) poll, select, 和 epoll 調(diào)用; 它在 中聲明, 這個(gè)文件必須被驅(qū)動(dòng)源碼包含. 驅(qū)動(dòng)編寫(xiě)者不必要知道所有它內(nèi)容并且必須作為一個(gè)不透明的對(duì)象使用它; 它被傳遞給驅(qū)動(dòng)方法以便驅(qū)動(dòng)可用每個(gè)能喚醒進(jìn)程的等待隊(duì)列來(lái)加載它, 并且可改變 poll 操作狀態(tài). 驅(qū)動(dòng)增加一個(gè)等待隊(duì)列到poll_table結(jié)構(gòu)通過(guò)調(diào)用函數(shù) poll_wait:

 void poll_wait (struct file *, wait_queue_head_t *, poll_table *);
 1

poll 方法的第 2 個(gè)任務(wù)是返回位掩碼, 它描述哪個(gè)操作可馬上被實(shí)現(xiàn); 這也是直接的. 例如, 如果設(shè)備有數(shù)據(jù)可用, 一個(gè)讀可能不必睡眠而完成; poll 方法應(yīng)當(dāng)指示這個(gè)時(shí)間狀態(tài). 幾個(gè)標(biāo)志(通過(guò) 定義)用來(lái)指示可能的操作: POLLIN:如果設(shè)備可被不阻塞地讀, 這個(gè)位必須設(shè)置. POLLRDNORM:這個(gè)位必須設(shè)置, 如果”正?!睌?shù)據(jù)可用來(lái)讀. 一個(gè)可讀的設(shè)備返回( POLLIN|POLLRDNORM ). POLLOUT:這個(gè)位在返回值中設(shè)置, 如果設(shè)備可被寫(xiě)入而不阻塞. …… poll的一個(gè)典型模板如下:

 static unsigned int globalfifo_poll(struct file *filp, poll_table *wait)
 {
     unsigned int mask = 0;
     struct globalfifo_dev *dev = filp->private_data; /*獲得設(shè)備結(jié)構(gòu)體指針*/
 
     down(&dev->sem);
 
     poll_wait(filp, &dev->r_wait, wait);
     poll_wait(filp, &dev->w_wait, wait);
     /*fifo非空*/
     if (dev->current_len != 0) {
         mask |= POLLIN | POLLRDNORM; /*標(biāo)示數(shù)據(jù)可獲得*/
     }
     /*fifo非滿(mǎn)*/
     if (dev->current_len != GLOBALFIFO_SIZE) {
         mask |= POLLOUT | POLLWRNORM; /*標(biāo)示數(shù)據(jù)可寫(xiě)入*/
     }
 
     up(&dev->sem);
     return mask;
 }123456789101112131415161718192021

應(yīng)用程序如何去使用這個(gè)poll呢?一般用select()來(lái)實(shí)現(xiàn),其原型為:

 int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
 1

其中,readfds, writefds, exceptfds,分別是被select()監(jiān)視的讀、寫(xiě)和異常處理的文件描述符集合。numfds是需要檢查的號(hào)碼最高的文件描述符加1。 以下是一個(gè)具體的例子:

 /*======================================================================
     A test program in userspace
     This example is to introduce the ways to use "select"
      and driver poll
 
     The initial developer of the original code is Baohua Song
     . All Rights Reserved.
 ======================================================================*/
 #include #include #include #include #include #include 
 #define FIFO_CLEAR 0x1
 #define BUFFER_LEN 20
 main()
 {
   int fd, num;
   char rd_ch[BUFFER_LEN];
   fd_set rfds,wfds;
 
   /*以非阻塞方式打開(kāi)/dev/globalmem設(shè)備文件*/
   fd = open("/dev/globalfifo", O_RDONLY | O_NONBLOCK);
   if (fd != - 1)
   {
     /*FIFO清0*/
     if (ioctl(fd, FIFO_CLEAR, 0)其中: FD_ZERO(fd_set *set); //清除一個(gè)文件描述符集set FD_SET(int fd, fd_set *set); //將一個(gè)文件描述符fd,加入到文件描述符集set中 FD_CLEAR(int fd, fd_set *set); //將一個(gè)文件描述符fd,從文件描述符集set中清除 FD_ISSET(int fd, fd_set *set); //判斷文件描述符fd是否被置位。22,并發(fā)與競(jìng)態(tài)介紹Linux設(shè)備驅(qū)動(dòng)中必須解決一個(gè)問(wèn)題是多個(gè)進(jìn)程對(duì)共享資源的并發(fā)訪問(wèn),并發(fā)的訪問(wèn)會(huì)導(dǎo)致競(jìng)態(tài),在當(dāng)今的Linux內(nèi)核中,支持SMP與內(nèi)核搶占的環(huán)境下,更是充滿(mǎn)了并發(fā)與競(jìng)態(tài)。幸運(yùn)的是,Linux 提供了多鐘解決競(jìng)態(tài)問(wèn)題的方式,這些方式適合不同的應(yīng)用場(chǎng)景。例如:中斷屏蔽、原子操作、自旋鎖、信號(hào)量等等并發(fā)控制機(jī)制。 并發(fā)與競(jìng)態(tài)的概念 并發(fā)是指多個(gè)執(zhí)行單元同時(shí)、并發(fā)被執(zhí)行,而并發(fā)的執(zhí)行單元對(duì)共享資源(硬件資源和軟件上的全局變量、靜態(tài)變量等)的訪問(wèn)則很容易導(dǎo)致競(jìng)態(tài)。 臨界區(qū)概念是為解決競(jìng)態(tài)條件問(wèn)題而產(chǎn)生的,一個(gè)臨界區(qū)是一個(gè)不允許多路訪問(wèn)的受保護(hù)的代碼,這段代碼可以操縱共享數(shù)據(jù)或共享服務(wù)。臨界區(qū)操縱堅(jiān)持互斥鎖原則(當(dāng)一個(gè)線程處于臨界區(qū)中,其他所有線程都不能進(jìn)入臨界區(qū))。然而,臨界區(qū)中需要解決的一個(gè)問(wèn)題是死鎖。23, 中斷屏蔽在單CPU 范圍內(nèi)避免競(jìng)態(tài)的一種簡(jiǎn)單而省事的方法是進(jìn)入臨界區(qū)之前屏蔽系統(tǒng)的中斷。CPU 一般都具有屏蔽中斷和打開(kāi)中斷的功能,這個(gè)功能可以保證正在執(zhí)行的內(nèi)核執(zhí)行路徑不被中斷處理程序所搶占,有效的防止了某些競(jìng)態(tài)條件的發(fā)送,總之,中斷屏蔽將使得中斷與進(jìn)程之間的并發(fā)不再發(fā)生。 中斷屏蔽的使用方法: local_irq_disable() /屏蔽本地CPU 中斷/
 …..
 critical section /臨界區(qū)受保護(hù)的數(shù)據(jù)/
 …..
 local_irq_enable() /打開(kāi)本地CPU 中斷/ 12345由于Linux 的異步I/O、進(jìn)程調(diào)度等很多重要操作都依賴(lài)于中斷,中斷對(duì)內(nèi)核的運(yùn)行非常重要,在屏蔽中斷期間的所有中斷都無(wú)法得到處理,因此長(zhǎng)時(shí)間屏蔽中斷是非常危險(xiǎn)的,有可能造成數(shù)據(jù)的丟失,甚至系統(tǒng)崩潰的后果。這就要求在屏蔽了中斷后,當(dāng)前的內(nèi)核執(zhí)行路徑要盡快地執(zhí)行完臨界區(qū)代碼。 與local_irq_disable()不同的是,local_irq_save(flags)除了進(jìn)行禁止中斷的操作外,還保存當(dāng)前CPU 的中斷狀態(tài)位信息;與local_irq_enable()不同的是,local_irq_restore(flags) 除了打開(kāi)中斷的操作外,還恢復(fù)了CPU 被打斷前的中斷狀態(tài)位信息。24, 原子操作原子操作指的是在執(zhí)行過(guò)程中不會(huì)被別的代碼路徑所中斷的操作,Linux 內(nèi)核提供了兩類(lèi)原子操作——位原子操作和整型原子操作。它們的共同點(diǎn)是在任何情況下都是原子的,內(nèi)核代碼可以安全地調(diào)用它們而不被打斷。然而,位和整型變量原子操作都依賴(lài)于底層CPU 的原子操作來(lái)實(shí)現(xiàn),因此這些函數(shù)的實(shí)現(xiàn)都與 CPU 架構(gòu)密切相關(guān)。 1 整型原子操作 1)、設(shè)置原子變量的值 void atomic_set(atomic v,int i); /設(shè)置原子變量的值為 i */
 atomic_t v = ATOMIC_INIT(0); /定義原子變量 v 并初始化為 0 / 122)、獲取原子變量的值 int atomic_read(atomic_t v) /返回原子變量 v 的當(dāng)前值*/
 13)、原子變量加/減 void atomic_add(int i,atomic_t v) /原子變量增加 i */
 void atomic_sub(int i,atomic_t v) /原子變量減少 i */
 124)、原子變量自增/自減 void atomic_inc(atomic_t v) /原子變量增加 1 */
 void atomic_dec(atomic_t v) /原子變量減少 1 */
 125)、操作并測(cè)試 int atomic_inc_and_test(atomic_t *v);
 int atomic_dec_and_test(atomic_t *v);
 int atomic_sub_and_test(int i, atomic_t *v);
 123上述操作對(duì)原子變量執(zhí)行自增、自減和減操作后測(cè)試其是否為 0 ,若為 0 返回true,否則返回false。注意:沒(méi)有atomic_add_and_test(int i, atomic_t *v)。 6)、操作并返回 int atomic_add_return(int i, atomic_t *v);
 int atomic_sub_return(int i, atomic_t *v);
 int atomic_inc_return(atomic_t *v);
 int atomic_dec_return(atomic_t *v);
 1234上述操作對(duì)原子變量進(jìn)行加/減和自增/自減操作,并返回新的值。 2 位原子操作 1)、設(shè)置位 void set_bit(nr,void addr);/設(shè)置addr 指向的數(shù)據(jù)項(xiàng)的第 nr 位為1 */
 12)、清除位 void clear_bit(nr,void addr)/設(shè)置addr 指向的數(shù)據(jù)項(xiàng)的第 nr 位為0 */
 13)、取反位 void change_bit(nr,void addr); /對(duì)addr 指向的數(shù)據(jù)項(xiàng)的第 nr 位取反操作*/
 14)、測(cè)試位 test_bit(nr,void addr);/返回addr 指向的數(shù)據(jù)項(xiàng)的第 nr位*/
 15)、測(cè)試并操作位 int test_and_set_bit(nr, void *addr);
 int test_and_clear_bit(nr,void *addr);
 int test_amd_change_bit(nr,void *addr);
 12325, 自旋鎖自旋鎖(spin lock)是一種典型的對(duì)臨界資源進(jìn)行互斥訪問(wèn)的手段。為了獲得一個(gè)自旋鎖,在某CPU 上運(yùn)行的代碼需先執(zhí)行一個(gè)原子操作,該操作測(cè)試并設(shè)置某個(gè)內(nèi)存變量,由于它是原子操作,所以在該操作完成之前其他執(zhí)行單元不能訪問(wèn)這個(gè)內(nèi)存變量。如果測(cè)試結(jié)果表明鎖已經(jīng)空閑,則程序獲得這個(gè)自旋鎖并繼續(xù)執(zhí)行;如果測(cè)試結(jié)果表明鎖仍被占用,則程序?qū)⒃谝粋€(gè)小的循環(huán)里面重復(fù)這個(gè)“測(cè)試并設(shè)置” 操作,即進(jìn)行所謂的“自旋”。 理解自旋鎖最簡(jiǎn)單的方法是把它當(dāng)做一個(gè)變量看待,該變量把一個(gè)臨界區(qū)標(biāo)記為“我在這運(yùn)行了,你們都稍等一會(huì)”,或者標(biāo)記為“我當(dāng)前不在運(yùn)行,可以被使用”。 Linux中與自旋鎖相關(guān)操作有: 1)、定義自旋鎖 spinlock_t my_lock;
 12)、初始化自旋鎖 spinlock_t my_lock = SPIN_LOCK_UNLOCKED; /靜態(tài)初始化自旋鎖/
 void spin_lock_init(spinlock_t lock); /動(dòng)態(tài)初始化自旋鎖*/
 123)、獲取自旋鎖 /若獲得鎖立刻返回真,否則自旋在那里直到該鎖保持者釋放/
 void spin_lock(spinlock_t *lock);
 /若獲得鎖立刻返回真,否則立刻返回假,并不會(huì)自旋等待/
 void spin_trylock(spinlock_t *lock)
 12344)、釋放自旋鎖 void spin_unlock(spinlock_t *lock)
 1自旋鎖的一般用法: spinlock_t lock; /定義一個(gè)自旋鎖/
 spin_lock_init(&lock); /動(dòng)態(tài)初始化一個(gè)自旋鎖/
 ……
 spin_lock(&lock); /獲取自旋鎖,保護(hù)臨界區(qū)/
 ……./臨界區(qū)/
 spin_unlock(&lock); /解鎖/ 123456自旋鎖主要針對(duì)SMP 或單CPU 但內(nèi)核可搶占的情況,對(duì)于單CPU 且內(nèi)核不支持搶占的系統(tǒng),自旋鎖退化為空操作。盡管用了自旋鎖可以保證臨界區(qū)不受別的CPU和本地CPU內(nèi)的搶占進(jìn)程打擾,但是得到鎖的代碼路徑在執(zhí)行臨界區(qū)的時(shí)候,還可能受到中斷和底半部(BH)的影響,為了防止這種影響,就需要用到自旋鎖的衍生。 獲取自旋鎖的衍生函數(shù): void spin_lock_irq(spinlock_t lock); /獲取自旋鎖之前禁止中斷*/
 void spin_lock_irqsave(spinlock_t lock, unsigned long flags);/獲取自旋鎖之前禁止中斷,并且將先前的中斷狀態(tài)保存在flags 中*/
 void spin_lock_bh(spinlock_t lock); /在獲取鎖之前禁止軟中斷,但不禁止硬件中斷*/ 123釋放自旋鎖的衍生函數(shù): void spin_unlock_irq(spinlock_t *lock)
 void spin_unlock_irqrestore(spinlock_t *lock,unsigned long flags);
 void spin_unlock_bh(spinlock_t *lock); 123解鎖的時(shí)候注意要一一對(duì)應(yīng)去解鎖。 自旋鎖注意點(diǎn): (1)自旋鎖實(shí)際上是忙等待,因此,只有占用鎖的時(shí)間極短的情況下,使用自旋鎖才是合理的。 (2)自旋鎖可能導(dǎo)致系統(tǒng)死鎖。 (3)自旋鎖鎖定期間不能調(diào)用可能引起調(diào)度的函數(shù)。如:copy_from_user()、copy_to_user()、kmalloc()、msleep()等函數(shù)。 (4)擁有自旋鎖的代碼是不能休眠的。26, 讀寫(xiě)自旋鎖它允許多個(gè)讀進(jìn)程并發(fā)執(zhí)行,但是只允許一個(gè)寫(xiě)進(jìn)程執(zhí)行臨界區(qū)代碼,而且讀寫(xiě)也是不能同時(shí)進(jìn)行的。 1)、定義和初始化讀寫(xiě)自旋鎖 rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 靜態(tài)初始化 */
 rwlock_t my_rwlock;
 rwlock_init(&my_rwlock); /* 動(dòng)態(tài)初始化 */
 1232)、讀鎖定 void read_lock(rwlock_t *lock);
 void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
 void read_lock_irq(rwlock_t *lock);
 void read_lock_bh(rwlock_t *lock); 12343)、讀解鎖 void read_unlock(rwlock_t *lock);
 void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
 void read_unlock_irq(rwlock_t *lock);
 void read_unlock_bh(rwlock_t *lock); 1234在對(duì)共享資源進(jìn)行讀取之前,應(yīng)該先調(diào)用讀鎖定函數(shù),完成之后調(diào)用讀解鎖函數(shù)。 4)、寫(xiě)鎖定 void write_lock(rwlock_t *lock);
 void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
 void write_lock_irq(rwlock_t *lock);
 void write_lock_bh(rwlock_t *lock);
 void write_trylock(rwlock_t *lock); 123455)、寫(xiě)解鎖 void write_unlock(rwlock_t *lock);
 void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
 void write_unlock_irq(rwlock_t *lock);
 void write_unlock_bh(rwlock_t *lock); 1234在對(duì)共享資源進(jìn)行寫(xiě)之前,應(yīng)該先調(diào)用寫(xiě)鎖定函數(shù),完成之后應(yīng)調(diào)用寫(xiě)解鎖函數(shù)。讀寫(xiě)自旋鎖的一般用法: rwlock_t lock; /定義一個(gè)讀寫(xiě)自旋鎖 rwlock/
 rwlock_init(&lock); /初始化/
 read_lock(&lock); /讀取前先獲取鎖/
 …../臨界區(qū)資源/
 read_unlock(&lock); /讀完后解鎖/
 write_lock_irqsave(&lock, flags); /寫(xiě)前先獲取鎖/
 …../臨界區(qū)資源/
 write_unlock_irqrestore(&lock,flags); /寫(xiě)完后解鎖/ 1234567827, 順序鎖(sequence lock)順序鎖是對(duì)讀寫(xiě)鎖的一種優(yōu)化,讀執(zhí)行單元在寫(xiě)執(zhí)行單元對(duì)被順序鎖保護(hù)的資源進(jìn)行寫(xiě)操作時(shí)仍然可以繼續(xù)讀,而不必等地寫(xiě)執(zhí)行單元完成寫(xiě)操作,寫(xiě)執(zhí)行單元也不必等待所有讀執(zhí)行單元完成讀操作才進(jìn)去寫(xiě)操作。但是,寫(xiě)執(zhí)行單元與寫(xiě)執(zhí)行單元依然是互斥的。并且,在讀執(zhí)行單元讀操作期間,寫(xiě)執(zhí)行單元已經(jīng)發(fā)生了寫(xiě)操作,那么讀執(zhí)行單元必須進(jìn)行重讀操作,以便確保讀取的數(shù)據(jù)是完整的,這種鎖對(duì)于讀寫(xiě)同時(shí)進(jìn)行概率比較小的情況,性能是非常好的。 順序鎖有個(gè)限制,它必須要求被保護(hù)的共享資源不包含有指針,因?yàn)閷?xiě)執(zhí)行單元可能使得指針失效,但讀執(zhí)行單元如果正要訪問(wèn)該指針,就會(huì)導(dǎo)致oops。 1)、初始化順序鎖 seqlock_t lock1 = SEQLOCK_UNLOCKED; /靜態(tài)初始化/
 seqlock lock2; /動(dòng)態(tài)初始化/
 seqlock_init(&lock2) 1232)、獲取順序鎖 void write_seqlock(seqlock_t *s1);
 void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags)
 void write_seqlock_irq(seqlock_t *lock);
 void write_seqlock_bh(seqlock_t *lock);
 int write_tryseqlock(seqlock_t *s1); 123453)、釋放順序鎖 void write_sequnlock(seqlock_t *s1);
 void write_sequnlock_irqsave(seqlock_t *lock, unsigned long flags)
 void write_sequnlock_irq(seqlock_t *lock);
 void write_sequnlock_bh(seqlock_t *lock); 1234寫(xiě)執(zhí)行單元使用順序鎖的模式如下: write_seqlock(&seqlock_a);
 /寫(xiě)操作代碼/
 ……..
 write_sequnlock(&seqlock_a); 12344)、讀開(kāi)始 unsigned read_seqbegin(const seqlock_t *s1);
 unsigned read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags); 125)、重讀 int read_seqretry(const seqlock_t *s1, unsigned iv);
 int read_seqretry_irqrestore(seqlock_t *lock,unsigned int seq,unsigned long flags); 12讀執(zhí)行單元使用順序鎖的模式如下: unsigned int seq;
 do{
 seq = read_seqbegin(&seqlock_a);
 /讀操作代碼/
 …….
 }while (read_seqretry(&seqlock_a, seq)); 12345628, 信號(hào)量信號(hào)量的使用 信號(hào)量(semaphore)是用于保護(hù)臨界區(qū)的一種最常用的辦法,它的使用方法與自旋鎖是類(lèi)似的,但是,與自旋鎖不同的是,當(dāng)獲取不到信號(hào)量的時(shí)候,進(jìn)程不會(huì)自旋而是進(jìn)入睡眠的等待狀態(tài)。 1)、定義信號(hào)量 struct semaphore sem;
 12)、初始化信號(hào)量 void sema_init(struct semaphore sem, int val); /初始化信號(hào)量的值為 val */
 1更常用的是下面這二個(gè)宏: #define init_MUTEX(sem) sema_init(sem, 1)
 #define init_MUTEX_LOCKED(sem) sem_init(sem, 0) 12然而,下面這兩個(gè)宏是定義并初始化信號(hào)量的“快捷方式” DECLARE_MUTEX(name) /一個(gè)稱(chēng)為name信號(hào)量變量被初始化為 1 /
 DECLARE_MUTEX_LOCKED(name) /一個(gè)稱(chēng)為name信號(hào)量變量被初始化為 0 / 123)、獲得信號(hào)量 /該函數(shù)用于獲取信號(hào)量,若獲取不成功則進(jìn)入不可中斷的睡眠狀態(tài)/
 void down(struct semaphore *sem);
 /該函數(shù)用于獲取信號(hào)量,若獲取不成功則進(jìn)入可中斷的睡眠狀態(tài)/
 void down_interruptible(struct semaphore *sem);
 /該函數(shù)用于獲取信號(hào)量,若獲取不成功立刻返回 -EBUSY/
 int down_trylock(struct sempahore *sem); 1234564)、釋放信號(hào)量 void up(struct semaphore sem); /釋放信號(hào)量 sem ,并喚醒等待者*/
 1信號(hào)量的一般用法: DECLARE_MUTEX(mount_sem); /定義一個(gè)信號(hào)量mount_sem,并初始化為 1 /
 down(&mount_sem); /* 獲取信號(hào)量,保護(hù)臨界區(qū)*/
 …..
 critical section /臨界區(qū)/
 …..
 up(&mount_sem); /釋放信號(hào)量/ 12345629, 讀寫(xiě)信號(hào)量讀寫(xiě)信號(hào)量可能引起進(jìn)程阻塞,但是它允許多個(gè)讀執(zhí)行單元同時(shí)訪問(wèn)共享資源,但最多只能有一個(gè)寫(xiě)執(zhí)行單元。 1)、定義和初始化讀寫(xiě)信號(hào)量 struct rw_semaphore my_rws; /定義讀寫(xiě)信號(hào)量/
 void init_rwsem(struct rw_semaphore sem); /初始化讀寫(xiě)信號(hào)量*/
 122)、讀信號(hào)量獲取 void down_read(struct rw_semaphore *sem);
 int down_read_trylock(struct rw_semaphore *sem);
 123)、讀信號(hào)量釋放 void up_read(struct rw_semaphore *sem);
 14)、寫(xiě)信號(hào)量獲取 void down_write(struct rw_semaphore *sem);
 int down_write_trylock(struct rw_semaphore *sem);
 125)、寫(xiě)信號(hào)量釋放 void up_write(struct rw_semaphore *sem);
 130, completion完成量(completion)用于一個(gè)執(zhí)行單元等待另外一個(gè)執(zhí)行單元執(zhí)行完某事。 1)、定義完成量 struct completion my_completion;
 12)、初始化完成量 init_completion(&my_completion);
 13)、定義并初始化的“快捷方式” DECLARE_COMPLETION(my_completion)
 14)、等待完成量 void wait_for_completion(struct completion c); /等待一個(gè) completion 被喚醒*/
 15)、喚醒完成量 void complete(struct completion c); /只喚醒一個(gè)等待執(zhí)行單元*/
 void complete(struct completion c); /喚醒全部等待執(zhí)行單元*/
 1231, 自旋鎖VS信號(hào)量信號(hào)量是進(jìn)程級(jí)的,用于多個(gè)進(jìn)程之間對(duì)資源的互斥,雖然也是在內(nèi)核中,但是該內(nèi)核執(zhí)行路徑是以進(jìn)程的身份,代表進(jìn)程來(lái)爭(zhēng)奪資源的。如果競(jìng)爭(zhēng)失敗,會(huì)發(fā)送進(jìn)程上下文切換,當(dāng)前進(jìn)程進(jìn)入睡眠狀態(tài),CPU 將運(yùn)行其他進(jìn)程。鑒于開(kāi)銷(xiāo)比較大,只有當(dāng)進(jìn)程資源時(shí)間較長(zhǎng)時(shí),選用信號(hào)量才是比較合適的選擇。然而,當(dāng)所要保護(hù)的臨界區(qū)訪問(wèn)時(shí)間比較短時(shí),用自旋鎖是比較方便的。 總結(jié): 解決并發(fā)與競(jìng)態(tài)的方法有(按本文順序): (1)中斷屏蔽 (2)原子操作(包括位和整型原子) (3)自旋鎖 (4)讀寫(xiě)自旋鎖 (5)順序鎖(讀寫(xiě)自旋鎖的進(jìn)化) (6)信號(hào)量 (7)讀寫(xiě)信號(hào)量 (8)完成量 其中,中斷屏蔽很少單獨(dú)被使用,原子操作只能針對(duì)整數(shù)進(jìn)行,因此自旋鎖和信號(hào)量應(yīng)用最為廣泛。自旋鎖會(huì)導(dǎo)致死循環(huán),鎖定期間內(nèi)不允許阻塞,因此要求鎖定的臨界區(qū)??;信號(hào)量允許臨界區(qū)阻塞,可以適用于臨界區(qū)大的情況。讀寫(xiě)自旋鎖和讀寫(xiě)信號(hào)量分別是放寬了條件的自旋鎖 信號(hào)量,它們?cè)试S多個(gè)執(zhí)行單元對(duì)共享資源的并發(fā)讀。至此關(guān)于Linux系統(tǒng)驅(qū)動(dòng)的教程分享結(jié)束,大家有任何問(wèn)題都可以在評(píng)論區(qū)留言啊。以上就是良許教程網(wǎng)為各位朋友分享的Linux系統(tǒng)相關(guān)內(nèi)容。想要了解更多Linux相關(guān)知識(shí)記得關(guān)注公眾號(hào)“良許Linux”,或掃描下方二維碼進(jìn)行關(guān)注,更多干貨等著你!

看完上述內(nèi)容,你們掌握Linux系統(tǒng)驅(qū)動(dòng)開(kāi)發(fā)的知識(shí)點(diǎn)有哪些的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI