国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

陽江市企業(yè)網(wǎng)站優(yōu)化企業(yè)如何進(jìn)行宣傳和推廣

陽江市企業(yè)網(wǎng)站優(yōu)化,企業(yè)如何進(jìn)行宣傳和推廣,沈陽網(wǎng)站建設(shè)找世紀(jì)興,自己學(xué)習(xí)建設(shè)網(wǎng)站文章目錄 信號進(jìn)程進(jìn)程通信線程可/不可重入函數(shù)線程同步互斥鎖條件變量自旋鎖讀寫鎖 I/O操作阻塞/非阻塞I/OI/O多路復(fù)用存儲映射I/O 信號 信號是事件發(fā)生時對進(jìn)程的通知機(jī)制,可以看做軟件中斷。信號與硬件中斷的相似之處在于其能夠打斷程序當(dāng)前執(zhí)行的正常流程。大多…

文章目錄

  • 信號
  • 進(jìn)程
  • 進(jìn)程通信
  • 線程
  • 可/不可重入函數(shù)
  • 線程同步
    • 互斥鎖
    • 條件變量
    • 自旋鎖
    • 讀寫鎖
  • I/O操作
    • 阻塞/非阻塞I/O
    • I/O多路復(fù)用
    • 存儲映射I/O

信號

信號是事件發(fā)生時對進(jìn)程的通知機(jī)制,可以看做軟件中斷。信號與硬件中斷的相似之處在于其能夠打斷程序當(dāng)前執(zhí)行的正常流程。大多數(shù)情況下無法預(yù)測信號達(dá)到的準(zhǔn)確時間,所以信號提供了一種處理異步事件的方法。
信號的目的就是用來通信的,通過信號將情況告知相應(yīng)的進(jìn)程,一個具有合適權(quán)限的進(jìn)程能夠向另一個進(jìn)程發(fā)送信號。
信號的產(chǎn)生條件包括:硬件發(fā)生異常、在終端下輸入了能夠產(chǎn)生信號的特殊字符(如CTRL+C產(chǎn)生中斷信號SIGINT)、用戶使用kill命令殺死進(jìn)程、軟件事件(比如定時器超時)等。
信號是異步的,產(chǎn)生信號的事件對進(jìn)程而言是隨機(jī)出現(xiàn)的,進(jìn)程無法預(yù)測該事件產(chǎn)生的準(zhǔn)確時間,進(jìn)程不能夠通過簡單地測試一個變量或使用系統(tǒng)調(diào)用來判斷是否產(chǎn)生了一個信號,這就如同硬件中斷事件,程序是無法得知中斷事件產(chǎn)生的具體時間,只有當(dāng)產(chǎn)生中斷事件時,才會告知程序、然后打斷當(dāng)前程序的正常執(zhí)行流程、跳轉(zhuǎn)去執(zhí)行中斷服務(wù)函數(shù),這就是異步處理方式。
信號本質(zhì)上是整型數(shù)字編號,相當(dāng)于硬件中斷所對應(yīng)的中斷號。內(nèi)核針對每個信號,都給其定義了一個唯一的整數(shù)編號,從數(shù)字1開始順序展開。


進(jìn)程

操作系統(tǒng)下的應(yīng)用程序在運行main()函數(shù)之前需要先執(zhí)行一段引導(dǎo)代碼,最終由這段引導(dǎo)代碼去調(diào)用應(yīng)用程序的main()函數(shù)。
進(jìn)程是一個動態(tài)過程,而不是靜態(tài)文件,它是程序的一次運行過程,當(dāng)應(yīng)用程序被加載到內(nèi)存中運行之后它就稱為了一個進(jìn)程,當(dāng)程序運行結(jié)束后也就意味著進(jìn)程終止,這就是進(jìn)程的一個生命周期。
Linux系統(tǒng)下的每一個進(jìn)程都有一個進(jìn)程號(PID),進(jìn)程號是一個正數(shù),用于唯一標(biāo)識系統(tǒng)中的某一個進(jìn)程。在應(yīng)用程序中,可通過系統(tǒng)調(diào)用getpid()來獲取本進(jìn)程的進(jìn)程號,通過getppid()來獲取父進(jìn)程的進(jìn)程號。

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);   //獲取本進(jìn)程的進(jìn)程號
pid_t getppid(void);  //獲取父進(jìn)程的進(jìn)程號

每一個進(jìn)程都有一組與其相關(guān)的環(huán)境變量,這些環(huán)境變量以字符串形式存儲在一個字符串?dāng)?shù)組列表中,把這個數(shù)組稱為環(huán)境列表。export命令可以添加或刪除一個環(huán)境變量。

env //查看環(huán)境變量
export LINUX_TEST=123456  //添加命令
export -n LINUX_TEST  //刪除命令

進(jìn)程的環(huán)境變量是從其父進(jìn)程中繼承過來的,新的進(jìn)程在創(chuàng)建之前,會繼承其父進(jìn)程的環(huán)境變量副本。
C語言程序由正文段、初始化數(shù)據(jù)段、未初始化數(shù)據(jù)段、棧、堆組成。
正文段也可稱為代碼段,這是CPU執(zhí)行的機(jī)器語言指令部分,文本段具有只讀屬性,以防止程序由于意外而修改其指令,正文段是可以共享的,即使在多個進(jìn)程間也可同時運行同一段程序。
初始化數(shù)據(jù)段通常稱為數(shù)據(jù)段,包含了顯式初始化的全局變量和靜態(tài)變量,當(dāng)程序加載到內(nèi)存中時,從可執(zhí)行文件中讀取這些變量的值。
未初始化數(shù)據(jù)段包含了未進(jìn)行顯式初始化的全局變量和靜態(tài)變量,通常將此段稱為bss段,這一名詞來源于早期匯編程序中的一個操作符,意思是由符號開始的塊(block started by symbol)。在程序開始執(zhí)行之前,系統(tǒng)會將本段內(nèi)所有內(nèi)存初始化為0,可執(zhí)行文件并沒有為bss段變量分配存儲空間,在可執(zhí)行文件中只需記錄bss段的位置及其所需大小,直到程序運行時,由加載器來分配這一段內(nèi)存空間。
棧是函數(shù)內(nèi)局部變量以及每次函數(shù)調(diào)用時所需保存信息的存放段,每次調(diào)用函數(shù)時,函數(shù)傳遞的實參以及函數(shù)返回值等都存放在棧中。棧是一個動態(tài)增長和收縮的段,由棧幀組成,系統(tǒng)會為每個當(dāng)前調(diào)用的函數(shù)分配一個棧幀,棧幀中存儲了函數(shù)的局部變量、實參和返回值。
堆是在程序運行時動態(tài)進(jìn)行內(nèi)存分配的一塊區(qū)域,譬如使用malloc()分配的內(nèi)存空間,就是從系統(tǒng)堆內(nèi)存中申請分配的。
使用 size 可執(zhí)行程序文件名 命令可以查看二進(jìn)制可執(zhí)行文件的文本段、數(shù)據(jù)段、bss段的段大小。
內(nèi)存布局由下圖所示。
在這里插入圖片描述
在Linux系統(tǒng)中,采用了虛擬內(nèi)存管理技術(shù),每一個進(jìn)程都在各自獨立的地址空間中運行,在32位系統(tǒng)中,每個進(jìn)程的邏輯地址空間均為4GB,這4GB的內(nèi)存空間按照3:1的比例進(jìn)行分配,其中用戶進(jìn)程享有3G的空間,而內(nèi)核獨自享有剩下的1G空間。
虛擬地址會通過硬件內(nèi)存管理單元映射到實際的物理地址空間中,建立虛擬地址到物理地址的映射關(guān)系后,對虛擬地址的讀寫操作實際上就是對物理地址的讀寫操作。
引入虛擬地址將其與物理地址空間隔離開有很多優(yōu)點。進(jìn)程與進(jìn)程、進(jìn)程與內(nèi)核相互隔離,提高了系統(tǒng)的安全性與穩(wěn)定性。兩個或者更多進(jìn)程能夠共享內(nèi)存,共享內(nèi)存可用于實現(xiàn)進(jìn)程間通信。便于實現(xiàn)內(nèi)存保護(hù)機(jī)制,編譯應(yīng)用程序時,無需關(guān)心鏈接地址,鏈接地址和運行地址必須一致才能使程序運行。
一個現(xiàn)有的進(jìn)程可以調(diào)用fork()函數(shù)創(chuàng)建一個新的進(jìn)程,原進(jìn)程和創(chuàng)建出來的子進(jìn)程都會從fork()函數(shù)的返回處繼續(xù)執(zhí)行,會導(dǎo)致調(diào)用fork()返回兩次,可通過返回值來區(qū)分子進(jìn)程和父進(jìn)程。fork()調(diào)用成功后,將會在父進(jìn)程中返回子進(jìn)程的PID,而在子進(jìn)程中返回值是0。調(diào)用失敗,父進(jìn)程返回值-1,不創(chuàng)建子進(jìn)程。
子進(jìn)程是父進(jìn)程的一個副本,它拷貝了父進(jìn)程的數(shù)據(jù)段、堆、棧以及繼承了父進(jìn)程打開的文件描述符,父進(jìn)程與子進(jìn)程并不共享這些存儲空間,這是子進(jìn)程對父進(jìn)程相應(yīng)部分存儲空間的完全復(fù)制,執(zhí)行fork()之后,每個進(jìn)程均可修改各自的棧數(shù)據(jù)以及堆段中的變量,而并不影響另一個進(jìn)程。父子進(jìn)程執(zhí)行相同的代碼段,在內(nèi)存中只存在一份代碼段數(shù)據(jù)。
下面代碼就是父子進(jìn)程在同一個文件中執(zhí)行寫操作。

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h>
#include <stdio.h>
#include <strings.h>
#include <error.h>
#include <stdlib.h>int main(int argc, char **argv)
{pid_t pid;int fd;fd = open("./aaa", O_RDWR);if(fd < 0){perror("open error");return fd;}pid = fork();switch(pid){case -1:perror("fork failed");break;case 0:write(fd, "1234",4);break;default:write(fd, "abab",4);break;}close(fd);return 0;
}

上面程序的執(zhí)行結(jié)果是"abab1234",先是父進(jìn)程寫,然后是子進(jìn)程寫,父子進(jìn)程實現(xiàn)了接續(xù)寫,每次寫都是從文件末尾開始寫入。
這個例子也說明,子進(jìn)程繼承了父進(jìn)程的文件描述符,兩個文件描述符都指向了一個文件表,所以它們的文件偏移量是同一個,子進(jìn)程改變文件的位置偏移量會作用到父進(jìn)程,父進(jìn)程改變文件的位置偏移量也會作用到子進(jìn)程。
如果在父子進(jìn)程中分別打開同一個文件進(jìn)行寫操作,就會出現(xiàn)數(shù)據(jù)被覆蓋的情況。
進(jìn)程號為1的進(jìn)程是所有進(jìn)程的父進(jìn)程,通常稱為init進(jìn)程,它是Linux系統(tǒng)啟動之后運行的第一個進(jìn)程,它管理著系統(tǒng)上所有其它進(jìn)程,init進(jìn)程由內(nèi)核啟動,理論上說它沒有父進(jìn)程。
父進(jìn)程先于子進(jìn)程結(jié)束,該子進(jìn)程將會成為孤兒進(jìn)程,在Linux系統(tǒng)當(dāng)中,所有的孤兒進(jìn)程都自動成為init進(jìn)程的子進(jìn)程。
進(jìn)程結(jié)束之后,通常需要其父進(jìn)程回收子進(jìn)程占用的一些內(nèi)存資源,父進(jìn)程通過調(diào)用wait()等函數(shù)回收子進(jìn)程資源并歸還給系統(tǒng)。
子進(jìn)程先于父進(jìn)程結(jié)束,而父進(jìn)程還未來得及回收子進(jìn)程占用的內(nèi)存資源,此時子進(jìn)程就變成了一個僵尸進(jìn)程。當(dāng)父進(jìn)程調(diào)用wait()函數(shù)回收子進(jìn)程資源后,僵尸進(jìn)程就會被內(nèi)核徹底刪除。如果父進(jìn)程并沒有調(diào)用wait()函數(shù)就退出了,那么此時init進(jìn)程將會接管它的子進(jìn)程并自動調(diào)用wait(),僵尸進(jìn)程將會被移除。
如果系統(tǒng)中存在大量的僵尸進(jìn)程,會阻礙新進(jìn)程的創(chuàng)建。而且僵尸進(jìn)程是無法通過信號將其殺死的,這種情況下只能殺死僵尸進(jìn)程的父進(jìn)程或等待其父進(jìn)程終止,這樣init進(jìn)程將會接管這些僵尸進(jìn)程,將它們從系統(tǒng)中清理掉。
Linux系統(tǒng)下進(jìn)程通常存在6種不同的狀態(tài),就緒態(tài)、運行態(tài)、僵尸態(tài)、可中斷睡眠狀態(tài)、不可中斷睡眠狀態(tài)和暫停態(tài)。
可中斷睡眠狀態(tài)也稱為淺度睡眠,可被信號喚醒,不可中斷睡眠狀態(tài)也稱深度睡眠,其不可被信號喚醒,等到相應(yīng)的條件成立才能結(jié)束睡眠。淺度睡眠和深度睡眠統(tǒng)稱為等待態(tài)或阻塞態(tài),表示進(jìn)程處于一種等待狀態(tài),等待某種條件成立之后便會進(jìn)入到就緒態(tài),處于等待態(tài)的進(jìn)程是無法參與進(jìn)程系統(tǒng)調(diào)度的。
每個進(jìn)程除了有一個進(jìn)程ID、父進(jìn)程ID之外,還有一個進(jìn)程組ID,用于標(biāo)識該進(jìn)程屬于哪一個進(jìn)程組,進(jìn)程組是一個或多個進(jìn)程的集合,這些進(jìn)程并不是孤立的,它們彼此之間或者存在父子、兄弟關(guān)系,或者在功能上有聯(lián)系。Linux系統(tǒng)設(shè)計進(jìn)程組實質(zhì)上是為了方便對進(jìn)程進(jìn)行管理,如果要終止某一個組內(nèi)的所有進(jìn)程,只需要終止進(jìn)程組即可,不需要逐個去結(jié)束。
守護(hù)進(jìn)程也稱為精靈進(jìn)程,是運行在后臺的一種特殊進(jìn)程,它獨立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些事情的發(fā)生。守護(hù)進(jìn)程的特點是長期運行、與控制終端脫離。Linux中大多數(shù)服務(wù)器就是用守護(hù)進(jìn)程實現(xiàn)的。
守護(hù)進(jìn)程是一種生存期很長的一種進(jìn)程,它們一般在系統(tǒng)啟動時開始運行,除非強行終止,否則直到系統(tǒng)關(guān)機(jī)都會保持運行。與守護(hù)進(jìn)程相比,普通進(jìn)程都是在用戶登錄或運行程序時創(chuàng)建,在運行結(jié)束或用戶注銷時終止,但守護(hù)進(jìn)程不受用戶登錄注銷的影響,它們將會一直運行著、直到系統(tǒng)關(guān)機(jī)。


進(jìn)程通信

進(jìn)程間通信(interprocess communication,簡稱IPC)指兩個進(jìn)程之間的通信,系統(tǒng)中的每一個進(jìn)程都有各自的地址空間,并且相互獨立、隔離,每個進(jìn)程都處于自己的地址空間中。同一個進(jìn)程的不同模塊之間進(jìn)行通信是比較簡單的,比如使用全局變量等。
兩個不同的進(jìn)程之間進(jìn)行通信通常是比較難的,因為這兩個進(jìn)程處于不同的地址空間中。
進(jìn)程間通信方式有管道、信號、消息隊列、信號量、共享內(nèi)存、套接字等。
管道包括普通管道pipe、流管道s_pipe和有名管道FIFO。
普通管道用于具有親緣關(guān)系的進(jìn)程間通信,如父子進(jìn)程、兄弟進(jìn)程,并且數(shù)據(jù)只能單向傳輸,如果要實現(xiàn)雙向傳輸,則必須要使用兩個管道。流管道以半雙工的方式實現(xiàn)雙向傳輸,但也只能在具有親緣關(guān)系的進(jìn)程間通信。有名管道即可實現(xiàn)雙向傳輸,也能在非親緣關(guān)系的進(jìn)程間通信。
信號用于通知接收信號的進(jìn)程有某種事件發(fā)生,可用于進(jìn)程間通信,進(jìn)程還可以發(fā)送信號給進(jìn)程本身。
消息隊列是消息的鏈表,存放在內(nèi)核中并由消息隊列標(biāo)識符標(biāo)識,消息隊列克服了信號傳遞信息少、管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺陷。消息隊列是UNIX下不同進(jìn)程之間實現(xiàn)共享資源的一種機(jī)制,UNIX允許不同進(jìn)程將格式化的數(shù)據(jù)流以消息隊列形式發(fā)送給任意進(jìn)程,有足夠權(quán)限的進(jìn)程可以向隊列中添加消息,被賦予讀權(quán)限的進(jìn)程則可以讀走隊列中的消息。
信號量是一個計數(shù)器,主要用于控制多個進(jìn)程間或一個進(jìn)程內(nèi)的多個線程間對共享資源的訪問,相當(dāng)于內(nèi)存中的標(biāo)志,進(jìn)程可以根據(jù)它判定是否能夠訪問某些共享資源,同時進(jìn)程也可以修改該標(biāo)志。除了用于共享資源的訪問控制外,還可用于進(jìn)程同步,信號量常作為一種鎖機(jī)制,防止某進(jìn)程在訪問資源時其它進(jìn)程也訪問該資源。
共享內(nèi)存就是映射一段能被其它進(jìn)程所訪問的內(nèi)存,這段共享內(nèi)存由一個進(jìn)程創(chuàng)建,但其它進(jìn)程都可以訪問,這使得多個進(jìn)程可以訪問同一塊內(nèi)存空間。共享內(nèi)存是最快的進(jìn)程間通信方式,它是針對其它進(jìn)程間通信方式運行效率低而專門設(shè)計的,它往往與其它通信機(jī)制來結(jié)合使用,比如信號量,以此實現(xiàn)進(jìn)程間的同步和通信。
Socket是基于網(wǎng)絡(luò)的進(jìn)程間通信方法,允許位于同一主機(jī)或使用網(wǎng)絡(luò)連接起來的不同主機(jī)上的應(yīng)用程序之間交換數(shù)據(jù),也就是網(wǎng)絡(luò)通信。


線程

線程是處理器任務(wù)調(diào)度和執(zhí)行的最小單位,它被包含在進(jìn)程之中,是進(jìn)程中的實際運行單位。一個線程指的是進(jìn)程中一個單一順序的控制流,一個進(jìn)程中可以創(chuàng)建多個線程,多個線程實現(xiàn)并發(fā)運行,每個線程執(zhí)行不同的任務(wù)。
當(dāng)一個程序啟動時,就有一個進(jìn)程被操作系統(tǒng)創(chuàng)建,與此同時一個線程也立刻運行,該線程通常叫做程序的主線程。應(yīng)用程序都是以main()函數(shù)做為入口開始運行的,所以main()函數(shù)就是主線程的入口函數(shù),main()函數(shù)所執(zhí)行的任務(wù)就是主線程需要執(zhí)行的任務(wù)。
任何一個進(jìn)程都包含一個主線程,只有主線程的進(jìn)程稱為單線程進(jìn)程。多線程指的是除了主線程以外,還有其它的線程,其它線程通常由主線程創(chuàng)建的,創(chuàng)建的新線程就是主線程的子線程。主線程通常會在最后結(jié)束運行,執(zhí)行各種清理工作,回收各個子線程占用的資源。
線程是程序最基本的運行單位,進(jìn)程不能運行,真正運行的是進(jìn)程中的線程。當(dāng)啟動應(yīng)用程序后,系統(tǒng)就創(chuàng)建了一個進(jìn)程,可以認(rèn)為進(jìn)程僅僅是一個容器,它包含了線程運行所需的數(shù)據(jù)結(jié)構(gòu)、環(huán)境變量等信息。同一進(jìn)程中的多個線程共享該進(jìn)程中的全部系統(tǒng)資源,如虛擬地址空間,文件描述符和信號處理等。同一進(jìn)程中的多個線程有各自的調(diào)用棧、寄存器環(huán)境、線程本地存儲等。
線程的特點:不是單獨存在,包含在進(jìn)程中;處理器任務(wù)調(diào)度和執(zhí)行的最小單位;可并發(fā)執(zhí)行;共享進(jìn)程資源,有相同的地址空間。
進(jìn)程創(chuàng)建多個子進(jìn)程可以實現(xiàn)并發(fā)處理多任務(wù),一個多線程進(jìn)程也可以實現(xiàn)并發(fā)處理多任務(wù)。
多進(jìn)程編程的劣勢:進(jìn)程間切換開銷大;進(jìn)程間通信麻煩。
多線程編程的優(yōu)勢:線程間的切換開銷比較小;同一進(jìn)程的多個線程在同一個地址空間中,因此通信容易;線程創(chuàng)建的速度遠(yuǎn)大于進(jìn)程創(chuàng)建的速度;多線程在多核處理器上更有優(yōu)勢。
串行就是一件事一件事接著做;并發(fā)是交替做不同的事;并行是同時做不同的事。并行運行情況下的多個執(zhí)行單元,每一個執(zhí)行單元同樣也可以以并發(fā)方式運行。
線程有其對應(yīng)的標(biāo)識線程ID,使用pthread_t數(shù)據(jù)類型來表示,一個線程可通過庫函數(shù)pthread_self()來獲取自己的線程ID。

#include <pthread.h>
pthread_t pthread_self(void);
int pthread_equal(pthread_t t1, pthread_t t2); //檢查兩個線程ID是否相等,相等返回非0值,不相等返回0

主線程可以使用庫函數(shù)pthread_create()負(fù)責(zé)創(chuàng)建一個新的線程,創(chuàng)建出來的新線程被稱為主線程的子線程。

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

thread保存新創(chuàng)建的線程ID;attr為線程屬性,設(shè)置為NULL表示所有屬性設(shè)置為默認(rèn)值;新創(chuàng)建的線程從start_routine()函數(shù)開始運行;arg是傳遞給start_routine()函數(shù)的參數(shù)。成功返回0,失敗時將返回一個錯誤號。
含有pthread_create函數(shù)時,在編譯的時候需要加上-lpthread,使用-l選項指定鏈接庫pthread,因為pthread不在gcc的默認(rèn)鏈接庫中,所以需要手動指定。

gcc -o test test.c -lpthread
gcc test.c -o test -lpthread

可使用下面的代碼進(jìn)行線程的創(chuàng)建。

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <stdlib.h>
#include <pthread.h>static void *new_thread_start(void *arg) 
{ printf("新線程: 進(jìn)程ID=%d 線程ID=%lu\n", getpid(), pthread_self()); return 0;
}int main(int argc, char **argv)
{pthread_t tid;int ret;ret = pthread_create(&tid, NULL, new_thread_start, NULL);if (ret) { printf("ret = %d\n", ret); exit(-1); }printf("主線程: 進(jìn)程ID=%d 線程ID=%lu\n", getpid(), pthread_self());sleep(1);exit(0);
}

終止線程可以在線程的start函數(shù)執(zhí)行return語句并返回指定值,返回值就是線程的退出碼;線程調(diào)用pthread_exit()函數(shù);調(diào)用pthread_cancel()取消線程。

#include <pthread.h>
void pthread_exit(void *retval);
int pthread_cancel(pthread_t thread);

如果進(jìn)程中的任意線程調(diào)用exit()、_exit()或者_(dá)Exit(),那么將會導(dǎo)致整個進(jìn)程終止。
主線程如果調(diào)用了pthread_exit(),那么主線程也會終止,但其它線程依然正常運行,直到進(jìn)程中的所有線程終止才會使得進(jìn)程終止。
使用下面的程序可以測試在主線程結(jié)束之后,整個進(jìn)程并沒有結(jié)束,新的線程還在運行。

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <stdlib.h>
#include <pthread.h>static void *new_thread_start(void *arg) 
{ printf("新線程start\n"); sleep(1); printf("新線程end\n"); pthread_exit(NULL);
}int main(int argc, char **argv)
{pthread_t tid;int ret;ret = pthread_create(&tid, NULL, new_thread_start, NULL);if (ret) { fprintf(stderr, "Error: %s\n", strerror(ret));//printf("ret = %d\n", ret); exit(-1); }printf("主線程end\n"); pthread_exit(NULL);exit(0);
}

調(diào)用pthread_join()函數(shù)來阻塞等待線程的終止,并獲取線程的退出碼,回收線程資源。

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

調(diào)用pthread_detach()將指定線程進(jìn)行分離,一個線程既可以將另一個線程分離,同時也可以將自己分離。

#include <pthread.h>
int pthread_detach(pthread_t thread);

一旦線程處于分離狀態(tài),就不能再使用pthread_join()來獲取其終止?fàn)顟B(tài),處于分離狀態(tài)之后便不能再恢復(fù)到之前的狀態(tài)。處于分離狀態(tài)的線程終止后,能夠自動回收線程資源。


可/不可重入函數(shù)

如果一個函數(shù)被同一進(jìn)程的多個不同的執(zhí)行流同時調(diào)用,每次函數(shù)調(diào)用總是能產(chǎn)生正確的結(jié)果,把這樣的函數(shù)就稱為可重入函數(shù)。
重入指的是同一個函數(shù)被不同執(zhí)行流調(diào)用,前一個執(zhí)行流還沒有執(zhí)行完該函數(shù),另一個執(zhí)行流又開始調(diào)用該函數(shù)了,就是同一個函數(shù)被多個執(zhí)行流并發(fā)/并行調(diào)用,在宏觀角度上理解指的就是被多個執(zhí)行流同時調(diào)用。
在多線程環(huán)境以及信號處理有關(guān)應(yīng)用程序中,需要注意不可重入函數(shù)的問題,如果多條執(zhí)行流同時調(diào)用一個不可重入函數(shù)則可能會得不到預(yù)期的結(jié)果、甚至有可能導(dǎo)致程序崩潰。不止是在應(yīng)用程序中,在一個包含了中斷處理的裸機(jī)應(yīng)用程序中亦是如此,所以不可重入函數(shù)通常存在著一定的安全隱患。
絕對可重入函數(shù)的特點:函數(shù)內(nèi)所使用到的變量均為局部變量,即函數(shù)內(nèi)的操作的內(nèi)存地址均為本地棧地址;函數(shù)參數(shù)和返回值均是值類型;函數(shù)內(nèi)調(diào)用的其它函數(shù)也均是絕對可重入函數(shù)。
如果函數(shù)內(nèi)部對全局變量只讀,且其他函數(shù)不修改這個全局變量,那么這個函數(shù)就是可重入函數(shù);如果函數(shù)內(nèi)部對全局變量要進(jìn)行修改,那么該函數(shù)就是不可重入函數(shù)。
不可重入函數(shù)不可以在它還沒有返回就再次被調(diào)用。函數(shù)不可重入大多數(shù)是因為在函數(shù)中引用了全局變量,比如,printf會引用全局變量stdout,malloc,free會引用全局的內(nèi)存分配表。
在unix里面通常都有加上_r后綴的同名可重入函數(shù)版本。
判斷一個函數(shù)是否為線程安全函數(shù)的方法是,該函數(shù)被多個線程同時調(diào)用是否總能產(chǎn)生正確的結(jié)果,如果每次都能產(chǎn)生預(yù)期的結(jié)果則表示該函數(shù)是一個線程安全函數(shù)。判斷一個函數(shù)是否為可重入函數(shù)的方法是,該函數(shù)被多個執(zhí)行流同時調(diào)用是否總能產(chǎn)生正確的結(jié)果,如果每次都能產(chǎn)生預(yù)期的結(jié)果則表示該函數(shù)是一個可重入函數(shù)??芍厝牒瘮?shù)是線程安全函數(shù),線程安全函數(shù)不一定是可重入函數(shù),因為不可重入函數(shù)可以通過互斥操作變?yōu)榫€程安全函數(shù)。


線程同步

線程同步是為了對共享資源的訪問進(jìn)行保護(hù),保護(hù)的目的是為了解決數(shù)據(jù)一致性問題,數(shù)據(jù)一致性問題的本質(zhì)就是進(jìn)程中多個線程對共享資源的并發(fā)訪問。
Linux系統(tǒng)提供了多種用于實現(xiàn)線程同步的機(jī)制,常見的方法有互斥鎖、條件變量、自旋鎖以及讀寫鎖等。

互斥鎖

互斥鎖也叫互斥量,在訪問共享資源之前對互斥鎖進(jìn)行上鎖,在訪問完成后釋放互斥鎖。對互斥鎖上鎖之后,任何其它試圖再次對互斥鎖進(jìn)行加鎖的線程都會被阻塞,直到當(dāng)前線程釋放互斥鎖,如果釋放互斥鎖時有一個以上的線程阻塞,那么這些阻塞的線程會被喚醒,它們都會嘗試對互斥鎖進(jìn)行加鎖,當(dāng)有一個線程成功對互斥鎖上鎖之后,其它線程就不能再次上鎖了,只能再次陷入阻塞,等待下一次解鎖。
互斥鎖初始化,加鎖,解鎖的函數(shù)如下。

#include <pthread.h> 
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

初始化的示例。

pthread_mutex_t mutex; 
pthread_mutex_init(&mutex, NULL);pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));  //動態(tài)分配
pthread_mutex_init(mutex, NULL);

對處于未鎖定狀態(tài)的互斥鎖進(jìn)行解鎖操作和解鎖由其它線程鎖定的互斥鎖都是錯誤的行為。
使用互斥鎖保護(hù)全局變量的代碼示例如下。

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <stdlib.h>
#include <pthread.h>static pthread_mutex_t mutex; 
static int g_count = 0;
static int loops;static void *new_thread_start(void *arg) 
{ int loops = *((int *)arg); int l_count, j;for (j = 0; j < loops; j++) { pthread_mutex_lock(&mutex); //互斥鎖上鎖 l_count = g_count; l_count++; g_count = l_count; pthread_mutex_unlock(&mutex);//互斥鎖解鎖 }
}int main(int argc, char **argv)
{pthread_t tid1, tid2;int ret;if(argc != 2){printf("error arguments!\n");return -1;}loops = atoi(argv[1]);  //接收用戶傳來的參數(shù)/* 初始化互斥鎖 */ pthread_mutex_init(&mutex, NULL);/* 創(chuàng)建2個新線程 */ret = pthread_create(&tid1, NULL, new_thread_start, &loops);if (ret) { fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(-1); }ret = pthread_create(&tid2, NULL, new_thread_start, &loops);if (ret) { fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(-1); }/* 等待線程結(jié)束 */ ret = pthread_join(tid1, NULL);if (ret) { fprintf(stderr, "pthread_join error: %s\n", strerror(ret));exit(-1); }ret = pthread_join(tid2, NULL);if (ret) { fprintf(stderr, "pthread_join error: %s\n", strerror(ret));exit(-1); }printf("g_count = %d\n", g_count);/* 銷毀互斥鎖 */pthread_mutex_destroy(&mutex);exit(0);
}

上面代碼中創(chuàng)建了2個線程,它們的開始函數(shù)都是new_thread_start,而在該開始函數(shù)中都會對g_count這個全局變量進(jìn)行修改,因此,對這個全局變量需要進(jìn)行加鎖,使得兩個線程能夠互斥修改訪問全局變量,這樣才能得到想要的結(jié)果。可以試著去掉互斥鎖,這種情況在傳入?yún)?shù)不大的情況下結(jié)果正確,但是當(dāng)參數(shù)較大時,沒有互斥鎖就會得到錯誤的結(jié)果。
當(dāng)互斥鎖已經(jīng)被其它線程鎖住時,調(diào)用pthread_mutex_lock()函數(shù)會被阻塞,直到互斥鎖解鎖。如果線程不希望被阻塞,可以使用pthread_mutex_trylock()函數(shù),該函數(shù)嘗試對互斥鎖進(jìn)行加鎖,如果互斥鎖處于未鎖住狀態(tài),那么調(diào)用pthread_mutex_trylock()將會鎖住互斥鎖并立馬返回,如果互斥鎖已經(jīng)被其它線程鎖住,調(diào)用pthread_mutex_trylock()加鎖失敗,但不會被阻塞,而是返回錯誤碼EBUSY。

int pthread_mutex_trylock(pthread_mutex_t *mutex);

調(diào)用pthread_mutex_destroy()函數(shù)來銷毀互斥鎖。

int pthread_mutex_destroy(pthread_mutex_t *mutex);

沒有解鎖的互斥鎖不能銷毀,沒有初始化的互斥鎖也不能銷毀!被pthread_mutex_destroy()銷毀之后的互斥鎖,不能再對其進(jìn)行上鎖和解鎖了,需要再次調(diào)用pthread_mutex_init()對互斥鎖進(jìn)行初始化之后才能使用。

條件變量

條件變量用于自動阻塞線程,知道某個特定事件發(fā)生或某個條件滿足為止,通常情況下,條件變量和互斥鎖一起搭配使用。
使用條件變量主要包括兩個動作:一個線程等待某個條件滿足而被阻塞;另一個線程中,條件滿足時發(fā)出信號。典型的例子就是生產(chǎn)者消費者問題。
條件變量初始化與銷毀函數(shù)如下。

#include <pthread.h> 
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond); 

條件變量的主要操作便是發(fā)送信號和等待,發(fā)送信號操作即是通知一個或多個處于等待狀態(tài)的線程,某個共享變量的狀態(tài)已經(jīng)改變,這些處于等待狀態(tài)的線程收到通知之后便會被喚醒,喚醒之后再檢查條件是否滿足,等待操作是指在收到一個通知前一直處于阻塞狀態(tài)。函數(shù)pthread_cond_signal()和pthread_cond_broadcast()均可向指定的條件變量發(fā)送信號,通知一個或多個處于等待狀態(tài)的線程。

int pthread_cond_broadcast(pthread_cond_t *cond);  //喚醒所有線程
int pthread_cond_signal(pthread_cond_t *cond);  //至少喚醒一個線程

判斷條件不滿足時,調(diào)用pthread_cond_wait()函數(shù)將線程設(shè)置為等待狀態(tài),即阻塞。

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

cond指向需要等待的條件變量;mutex指向一個互斥鎖對象。該函數(shù)內(nèi)部會對參數(shù)mutex所指定的互斥鎖進(jìn)行操作,在函數(shù)調(diào)用之前該線程已經(jīng)對互斥鎖加鎖了,調(diào)用者把互斥鎖傳遞給函數(shù),函數(shù)會自動把調(diào)用線程放到等待條件的線程列表上,然后將互斥鎖解鎖,當(dāng)該線程被喚醒時,會再次給互斥鎖上鎖。
條件變量和互斥鎖的結(jié)合使用示例如下。

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <stdlib.h>
#include <pthread.h>static pthread_mutex_t mutex;   //定義互斥鎖
static pthread_cond_t cond;     //定義條件變量
static int product = 0;  //全局共享資源/* 消費者線程 */
static void *consumer_thread(void *arg) 
{ while(1){ pthread_mutex_lock(&mutex);   //互斥鎖上鎖 while(product <= 0)pthread_cond_wait(&cond, &mutex);    //等待條件滿足while(product > 0){product--;    //條件滿足,消費printf("consume -1\n");}pthread_mutex_unlock(&mutex);   //互斥鎖解鎖 }
}int main(int argc, char **argv)
{pthread_t tid;int ret;/* 初始化互斥鎖和條件變量 */ pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);/* 創(chuàng)建新線程 */ret = pthread_create(&tid, NULL, consumer_thread, NULL);if (ret) { fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(-1); }while(1){pthread_mutex_lock(&mutex);   //互斥鎖上鎖product++;  //生產(chǎn)printf("produce +1\n");pthread_mutex_unlock(&mutex);   //互斥鎖解鎖pthread_cond_signal(&cond);    //向條件變量發(fā)送信號sleep(1); }exit(0);
}

一開始將產(chǎn)品數(shù)量設(shè)置為0,生產(chǎn)者每秒生產(chǎn)一個,消費者沒有時間限制,但是得等到有產(chǎn)品后才能消費,因此,上面代碼的運行結(jié)果是,生產(chǎn)者每秒鐘生產(chǎn)一個,消費者馬上消費掉,然后生產(chǎn)者再生產(chǎn),消費者等待消費這樣。

自旋鎖

自旋鎖與互斥鎖很相似,在訪問共享資源之前對自旋鎖進(jìn)行上鎖,在訪問完成后釋放自旋鎖。從實現(xiàn)方式上來說,互斥鎖是基于自旋鎖來實現(xiàn)的,所以自旋鎖相較于互斥鎖更加底層。
如果在獲取自旋鎖時,其處于未鎖定狀態(tài),那么線程將對自旋鎖上鎖,如果在獲取自旋鎖時,其已經(jīng)處于鎖定狀態(tài)了,那么獲取鎖操作將會在原地“自旋”,直到該自旋鎖的持有者釋放了鎖。自旋鎖與互斥鎖相似,但是互斥鎖在無法獲取到鎖時會讓線程陷入阻塞等待狀態(tài),而自旋鎖在無法獲取到鎖時,將會在原地“自旋”等待。“自旋”其實就是調(diào)用者一直在循環(huán)查看該自旋鎖的持有者是否已經(jīng)釋放了鎖。
自旋鎖的不足在于其在未獲得鎖的情況下一直占用著CPU,因為自旋鎖一直處于“自旋”等待狀態(tài),如果不能在很短的時間內(nèi)獲取鎖,這種操作會使CPU效率降低?;コ怄i的休眠與喚醒開銷是比較大的,自旋鎖的效率要比互斥鎖高。
自旋鎖通常用于需要保護(hù)的代碼段執(zhí)行時間很短,這樣就會使得持有鎖的線程會很快釋放鎖,而“自旋”等待的線程也只需等待很短的時間,這種情況下使用自旋鎖效率就會比較高。自旋鎖在內(nèi)核代碼中使用比較多。
自旋鎖的初始化與銷毀函數(shù)如下。

#include <pthread.h> 
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock); 

自旋鎖加鎖和解鎖的函數(shù)如下。

int pthread_spin_lock(pthread_spinlock_t *lock);     //未獲取到鎖時自旋等待
int pthread_spin_trylock(pthread_spinlock_t *lock);   //未獲取到鎖時返回錯誤,錯誤碼為EBUSY
int pthread_spin_unlock(pthread_spinlock_t *lock);

使用自旋鎖的示例如下。

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <stdlib.h>
#include <pthread.h>static pthread_spinlock_t spin;   //定義自旋鎖 
static int g_count = 0;  //全局變量
static int loops;static void *new_thread_start(void *arg) 
{ int loops = *((int *)arg); int l_count, j;for (j = 0; j < loops; j++) { pthread_spin_lock(&spin);   //自旋鎖上鎖l_count = g_count; l_count++; g_count = l_count; pthread_spin_unlock(&spin);  //自旋鎖解鎖 }
}int main(int argc, char **argv)
{pthread_t tid1, tid2;int ret;if(argc != 2){printf("error arguments!\n");return -1;} loops = atoi(argv[1]);  //接收用戶傳來的參數(shù)/* 初始化自旋鎖 */ pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE);/* 創(chuàng)建2個新線程 */ret = pthread_create(&tid1, NULL, new_thread_start, &loops);if (ret) { fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(-1); }ret = pthread_create(&tid2, NULL, new_thread_start, &loops);if (ret) { fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(-1); }/* 等待線程結(jié)束 */ ret = pthread_join(tid1, NULL);if (ret) { fprintf(stderr, "pthread_join error: %s\n", strerror(ret));exit(-1); }ret = pthread_join(tid2, NULL);if (ret) { fprintf(stderr, "pthread_join error: %s\n", strerror(ret));exit(-1); }printf("g_count = %d\n", g_count);/* 銷毀自旋鎖 */pthread_spin_destroy(&spin);exit(0);
}

讀寫鎖

互斥鎖或自旋鎖只有加鎖和不加鎖兩種狀態(tài),而且一次只有一個線程可以對其加鎖。讀寫鎖有三種狀態(tài),讀模式下的加鎖狀態(tài)、寫模式下的加鎖狀態(tài)和不加鎖狀態(tài),一次只有一個線程可以占有寫模式的讀寫鎖,但是可以有多個線程同時占有讀模式的讀寫鎖。讀寫鎖比互斥鎖具有更高的并行性,讀寫鎖非常適合于對共享數(shù)據(jù)讀的次數(shù)遠(yuǎn)大于寫的次數(shù)的情況。
當(dāng)讀寫鎖處于寫加鎖狀態(tài)時,在這個鎖被解鎖之前,所有試圖對這個鎖進(jìn)行的加鎖操作,不管是以讀模式還是以寫模式加鎖的線程都會被阻塞。當(dāng)讀寫鎖處于讀加鎖狀態(tài)時,所有試圖以讀模式對它進(jìn)行加鎖的線程都可以加鎖成功;但是任何以寫模式對它進(jìn)行加鎖的線程都會被阻塞,直到所有持有讀模式鎖的線程釋放它們的鎖為止。
讀寫鎖也叫做共享互斥鎖,當(dāng)讀寫鎖是讀模式鎖住時,就可以說成是共享模式鎖住,當(dāng)它是寫模式鎖住時,就可以說成是互斥模式鎖住。
讀寫鎖的初始化與銷毀函數(shù)如下。

#include <pthread.h> 
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 

讀寫鎖加鎖和解鎖的函數(shù)如下。

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);  //以讀模式對讀寫鎖進(jìn)行上鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);   //以寫模式對讀寫鎖進(jìn)行上鎖
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);  //解鎖

需要注意的是,不管是以讀模式還是寫模式對讀寫鎖進(jìn)行上鎖,解鎖的函數(shù)都是一樣的。
當(dāng)讀寫鎖處于寫模式加鎖狀態(tài)時,其它線程調(diào)用pthread_rwlock_rdlock()或pthread_rwlock_wrlock()函數(shù)均會獲取鎖失敗,從而陷入阻塞等待狀態(tài);當(dāng)讀寫鎖處于讀模式加鎖狀態(tài)時,其它線程調(diào)用pthread_rwlock_rdlock()函數(shù)可以成功獲取到鎖,如果調(diào)用pthread_rwlock_wrlock()函數(shù)則不能獲取到鎖,從而陷入阻塞等待狀態(tài)。
如果線程不希望被阻塞,可以調(diào)用pthread_rwlock_tryrdlock()和pthread_rwlock_trywrlock()來嘗試加鎖,如果不可以獲取鎖時,這兩個函數(shù)都會立馬返回錯誤,錯誤碼為EBUSY。

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); 
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

讀寫鎖的代碼示例如下。

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <stdlib.h>
#include <pthread.h>static pthread_rwlock_t rwlock;  //定義讀寫鎖 
static int g_count = 0;
static int nums[5] = {0, 1, 2, 3, 4};static void *read_thread(void *arg) 
{ int number = *((int *)arg);int j;for (j = 0; j < 10; j++) { pthread_rwlock_rdlock(&rwlock); //以讀模式獲取鎖printf("讀線程<%d>, g_count=%d\n", number+1, g_count); pthread_rwlock_unlock(&rwlock); //解鎖 sleep(1);}
}static void *write_thread(void *arg) 
{ int number = *((int *)arg);int j;for (j = 0; j < 10; j++) { pthread_rwlock_wrlock(&rwlock); //以寫模式獲取鎖printf("寫線程<%d>, g_count=%d\n", number+1, g_count+=20); pthread_rwlock_unlock(&rwlock); //解鎖 sleep(1);}
}int main(int argc, char **argv)
{pthread_t tid[10];int j;/* 對讀寫鎖進(jìn)行初始化 */ pthread_rwlock_init(&rwlock, NULL);/* 創(chuàng)建5個讀g_count變量的線程 */ for (j = 0; j < 5; j++) pthread_create(&tid[j], NULL, read_thread, &nums[j]);/* 創(chuàng)建5個寫g_count變量的線程 */ for (j = 0; j < 5; j++) pthread_create(&tid[j+5], NULL, write_thread, &nums[j]);/* 等待線程結(jié)束 */ for (j = 0; j < 10; j++) pthread_join(tid[j], NULL);  //回收線程/* 銷毀自旋鎖 */pthread_rwlock_destroy(&rwlock);exit(0);
}

上面代碼的運行結(jié)果如下圖所示。
在這里插入圖片描述
由上圖的運行結(jié)果可以看到,讀的線程讀到的值肯定是最新寫進(jìn)去的,而且寫操作每執(zhí)行一次,全局變量的數(shù)值就加20。


I/O操作

阻塞/非阻塞I/O

阻塞其實就是進(jìn)入了休眠狀態(tài),交出了CPU控制權(quán),wait()、pause()、sleep()等函數(shù)都會進(jìn)入阻塞。
對于有些文件,比如管道文件,如果當(dāng)前無數(shù)據(jù)可讀,那么讀操作可能會使調(diào)用者阻塞,直到有數(shù)據(jù)可讀時才被喚醒,這是阻塞式I/O;普通文件的讀寫操作是以非阻塞的方式進(jìn)行I/O操作的。
使用open函數(shù)時,為參數(shù)flags指定O_NONBLOCK標(biāo)志,文件就會以非阻塞方式進(jìn)行,如果不指定O_NONBLOCK標(biāo)志,則默認(rèn)以阻塞方式打開。
在ubuntu下的/dev/input目錄下使用下面的命令測試鼠標(biāo)和鍵盤。

sudo od -x /dev/input/event1
sudo od -x /dev/input/event2

具體對應(yīng)的哪個事件要自己測試,我的鍵盤對應(yīng)event1,鼠標(biāo)對應(yīng)event2,執(zhí)行上面的命令后,動一下鼠標(biāo)或者敲擊鍵盤上的按鍵都會有數(shù)據(jù)輸出,如下圖所示。
在這里插入圖片描述
可以寫如下測試程序測試鼠標(biāo)。

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <stdlib.h>
#include <pthread.h>int main(int argc, char **argv)
{char buf[100]; int fd, ret;fd = open("/dev/input/event2", O_RDONLY | O_NONBLOCK); if (fd < 0) { perror("open error"); exit(-1);}memset(buf, 0, sizeof(buf)); while(1){   ret = read(fd, buf, sizeof(buf)); if (ret > 0) { printf("成功讀取%d個字節(jié)數(shù)據(jù)\n", ret); }}close(fd);exit(0);
}

使用上面的程序以非阻塞方式打開鼠標(biāo)對應(yīng)的文件,鼠標(biāo)不移動時讀取到的字節(jié)數(shù)是48,移動的時候讀取到的字節(jié)數(shù)就是72,如下圖所示。
在這里插入圖片描述
當(dāng)對文件進(jìn)行讀取操作時,如果文件當(dāng)前無數(shù)據(jù)可讀,那么阻塞式I/O會將調(diào)用者應(yīng)用程序掛起,進(jìn)入休眠阻塞狀態(tài),直到有數(shù)據(jù)可讀時才會解除阻塞。對于非阻塞I/O,應(yīng)用程序不會被掛起,而是會立即返回,它要么一直輪訓(xùn)等待,直到數(shù)據(jù)可讀,要么直接放棄。阻塞式I/O的優(yōu)點在于能夠提升CPU的處理效率,當(dāng)自身條件不滿足時,進(jìn)入阻塞狀態(tài),交出CPU資源供其他人使用。
鍵盤是標(biāo)準(zhǔn)輸入設(shè)備stdin,進(jìn)程會自動從父進(jìn)程中繼承標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出以及標(biāo)準(zhǔn)錯誤,標(biāo)準(zhǔn)輸入設(shè)備對應(yīng)的文件描述符為0,所以測試鍵盤的時候直接將文件描述符設(shè)置為0即可。鍵盤的測試如下圖所示。
在這里插入圖片描述
鍵盤是阻塞方式讀的,將其設(shè)置為非阻塞方式使用fcntl()函數(shù),設(shè)置的代碼如下。

int flag;
flag = fcntl(0, F_GETFL); //先獲取原來的flag
flag |= O_NONBLOCK; //將O_NONBLOCK標(biāo)志添加到flag
fcntl(0, F_SETFL, flag); //重新設(shè)置flag

并發(fā)的讀取鼠標(biāo)和鍵盤的代碼示例如下。

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <stdlib.h>
#include <pthread.h>int main(int argc, char **argv)
{char buf[100]; int fd, ret, flag;/* 打開鼠標(biāo)設(shè)備文件 */fd = open("/dev/input/event2", O_RDONLY | O_NONBLOCK); if (fd < 0) { perror("open error"); exit(-1);} /* 將鍵盤設(shè)置為非阻塞方式 */flag = fcntl(0, F_GETFL);   //先獲取原來的flagflag |= O_NONBLOCK;     //將O_NONBLOCK標(biāo)志添加到flagfcntl(0, F_SETFL, flag);   //重新設(shè)置flagwhile(1){   ret = read(fd, buf, sizeof(buf));   //讀鼠標(biāo)if (ret > 0) { printf("鼠標(biāo)---成功讀取%d個字節(jié)數(shù)據(jù)\n", ret); }ret = read(0, buf, sizeof(buf));     //讀鍵盤if (ret > 0) { printf("鍵盤---成功讀取%d個字節(jié)數(shù)據(jù)\n", ret); }}close(fd);exit(0);
}

程序運行結(jié)果如下圖所示。
在這里插入圖片描述

I/O多路復(fù)用

I/O多路復(fù)用通過一種機(jī)制可以監(jiān)視多個文件描述符,一旦某個文件描述符可以執(zhí)行I/O操作時,系統(tǒng)就能夠通知應(yīng)用程序進(jìn)行相應(yīng)的讀寫操作。I/O多路復(fù)用技術(shù)是為了解決在并發(fā)式I/O場景中進(jìn)程或線程阻塞到某個I/O系統(tǒng)調(diào)用而出現(xiàn)的技術(shù),使進(jìn)程不阻塞于某個特定的I/O系統(tǒng)調(diào)用。I/O多路復(fù)用明顯的特征是外部阻塞式,內(nèi)部監(jiān)視多路I/O。
系統(tǒng)調(diào)用select()或者poll()函數(shù)可用于執(zhí)行I/O多路復(fù)用操作,調(diào)用函數(shù)后會一直阻塞,直到某一個或多個文件描述符成為就緒態(tài)。

#include <sys/select.h> 
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

存儲映射I/O

存儲映射I/O是一種基于內(nèi)存區(qū)域的高級I/O操作,它能將一個文件映射到進(jìn)程地址空間中的一塊內(nèi)存區(qū)域中,當(dāng)從這段內(nèi)存中讀數(shù)據(jù)時,就相當(dāng)于讀文件中的數(shù)據(jù),將數(shù)據(jù)寫入這段內(nèi)存時,則相當(dāng)于將數(shù)據(jù)直接寫入文件中。這樣就可以在不使用基本I/O操作函數(shù)read()和write()的情況下執(zhí)行I/O操作。
普通I/O方式一般是通過調(diào)用read()和write()函數(shù)來實現(xiàn)對文件的讀寫,使用read()和write()讀寫文件時,函數(shù)經(jīng)過層層的調(diào)用后,才能夠最終操作到文件,中間涉及到很多的函數(shù)調(diào)用過程,數(shù)據(jù)需要在不同的緩存間傳遞,效率會比較低。
比如復(fù)制一個文件,普通I/O方式首先需要將源文件中的數(shù)據(jù)讀取出來存放在一個應(yīng)用層緩沖區(qū)中,接著再將緩沖區(qū)中的數(shù)據(jù)寫入到目標(biāo)文件中。
在這里插入圖片描述
對于存儲映射I/O來說,由于源文件和目標(biāo)文件都已映射到了應(yīng)用層的內(nèi)存區(qū)域中,所以直接操作映射區(qū)來實現(xiàn)文件復(fù)制。
在這里插入圖片描述
存儲映射I/O將文件映射到應(yīng)用程序地址空間中的一塊內(nèi)存區(qū)域中,即映射區(qū),將磁盤文件與映射區(qū)關(guān)聯(lián)起來,不用再調(diào)用read()、write(),直接對映射區(qū)的文件進(jìn)行讀寫操作即可操作磁盤上的文件,而磁盤文件中的數(shù)據(jù)也可反應(yīng)到映射區(qū)中,這就是一種共享,可以認(rèn)為映射區(qū)就是應(yīng)用層與內(nèi)核層之間的共享內(nèi)存。
存儲映射I/O方式的不足是其所映射的文件只能是固定大小,因為文件所映射的區(qū)域已經(jīng)在調(diào)用mmap()函數(shù)時通過length參數(shù)指定了。此外,文件映射的內(nèi)存區(qū)域的大小必須是系統(tǒng)頁大小的整數(shù)倍,比如映射文件的大小為96字節(jié),假定系統(tǒng)頁大小為4096字節(jié),那么剩余的4000字節(jié)全部填充為0,雖然可以通過映射地址訪問剩余的這些字節(jié)數(shù)據(jù),但不能在映射文件中反應(yīng)出來,由此可知,使用存儲映射I/O在進(jìn)行大數(shù)據(jù)量操作時比較有效,對于少量數(shù)據(jù),使用普通I/O方式更加方便。
存儲映射I/O在處理大量數(shù)據(jù)時效率要比普通I/O高。


參考資料:
I.MX6U嵌入式Linux C應(yīng)用編程指南V1.4——正點原子

http://m.aloenet.com.cn/news/45493.html

相關(guān)文章:

  • 網(wǎng)站建設(shè)優(yōu)化服務(wù)特色高端網(wǎng)站設(shè)計
  • 重慶唐卡裝飾公司深圳市企業(yè)網(wǎng)站seo
  • 建網(wǎng)站開源代碼全國新冠疫情最新消息
  • wordpress用戶名忘記密碼廣州seo站內(nèi)優(yōu)化
  • 網(wǎng)站內(nèi)容建設(shè)的原則是什么意思整合營銷策略有哪些
  • 用老域名做網(wǎng)站還是新域名武漢seo首頁優(yōu)化技巧
  • ??谧鼍W(wǎng)站公司哪家好網(wǎng)頁快照
  • 網(wǎng)站工程師的職責(zé)網(wǎng)站推廣的6個方法是什么
  • url怎么做網(wǎng)站百度上海分公司
  • 網(wǎng)絡(luò)營銷推廣方案pdf站長工具seo綜合查詢
  • soho外貿(mào)建站拼多多seo 優(yōu)化軟件
  • 網(wǎng)站登錄不上怎么回事站長是什么職位
  • 電子工程網(wǎng)官方網(wǎng)站網(wǎng)址怎么注冊
  • 做搜狗網(wǎng)站優(yōu)化搜索數(shù)據(jù)
  • 網(wǎng)站域名設(shè)計推薦百度推廣培訓(xùn)班
  • 網(wǎng)站建設(shè)遠(yuǎn)程工作搜索引擎優(yōu)化方案
  • 網(wǎng)站建設(shè)前期預(yù)算端點seo博客
  • 物流企業(yè)網(wǎng)站有哪些百度網(wǎng)站優(yōu)化排名
  • 做公司網(wǎng)站 找誰做網(wǎng)絡(luò)營銷主要學(xué)什么
  • 做網(wǎng)站 信息集成過程的順序品牌營銷策略案例
  • UE做的比較好的網(wǎng)站軟文的概念是什么
  • 開獎網(wǎng)站怎么做營銷推廣網(wǎng)
  • 長春老火車站圖片如何宣傳推廣自己的產(chǎn)品
  • 用網(wǎng)站做淘客怎么做株洲seo優(yōu)化推薦
  • 房地產(chǎn)銷售自我介紹大兵seo博客
  • 淘寶網(wǎng)站是什么語言做的qq群推廣
  • 政府大型門戶網(wǎng)站建設(shè)方案seo專業(yè)培訓(xùn)班
  • 如何做旅游網(wǎng)站的旅行家網(wǎng)址推廣
  • 網(wǎng)站規(guī)劃書包括哪些方面公司官網(wǎng)怎么制作
  • 教務(wù)系統(tǒng)網(wǎng)站怎么做南寧網(wǎng)站seo外包