瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工藝,搭載一顆四核Cortex-A55處理器和Mali G52 2EE圖形處理器。RK3568支持4K解碼和1080P編碼,支持SATA/PCIE/USB3.0外圍接口。RK3568內置獨立NPU,可用于輕量級人工智能應用。RK3568支持安卓11和linux系統,主要面向物聯網網關、NVR存儲、工控平板、工業檢測、工控盒、卡拉OK、云終端、車載中控等行業。
?

迅為RK568郵票孔版開發板:
還在為工控機性能不足而煩惱?
還在為開發周期長而焦慮?
迅為RK568郵票孔版開發板,為您提供一站式解決方案!
強勁性能,賦能工業未來:
搭載瑞芯微RK568高性能處理器,采用先進制程工藝,提供澎湃算力,輕松應對復雜工業場景。
支持多種操作系統,如Android、Linux等,滿足不同應用需求。
豐富接口,擴展性強,可連接多種工業設備,構建穩定可靠的工業控制系統。
郵票孔設計,靈活易用:
采用郵票孔連接方式,方便快捷,節省空間,易于集成到各種工控設備中。
提供配套外殼,防護等級高,適應各種惡劣工業環境。
提供完善的技術支持和開發資料,助您快速上手,縮短開發周期。
廣泛應用于工業自動化控制、機器視覺、智能網關、工業機器人、物聯網終端等。

【公眾號】迅為電子
第19章 并發與競爭實驗
在前面章節的學習中,相信大家已經對用戶空間與內核空間數據傳遞進行了實驗,假如要傳遞的數據被存放在了全局變量,該數據就可以作為共享資源被多個任務共同讀寫,從而造成數據的錯誤傳輸,多個程序同時訪問一個共享資源產生的問題就叫做競爭。競爭產生的根本原因就是Linux系統的并發訪問。
在本章節中首先會對并發與并行的概念進行講解,隨后對競爭產生的原因進行總結,最后以一個實際的競爭實驗加深大家的理解。下面就讓我們開始本章節的學習吧。
19.1并發與競爭
19.1.1并發
早期計算機大多只有一個CPU核心,一個CPU在同一時間只能執行一個任務,當系統中有多個任務等待執行時,CPU只能執行完一個再執行下一個。而計算機的很多指令會涉及I/O操作,執行速度遠遠低于CPU內高速存儲器的存取速度,這就導致CPU經常處于空閑狀態,只能等待I/O操作完成后才能繼續執行后面的指令。為了提高CPU利用率,減少等待時間,提出了CPU并發工作理論。
所謂并發,就是通過算法將CPU資源合理地分配給多個任務,當一個任務執行I/O操作時,CPU可以轉而執行其它的任務,等到I/O操作完成以后,或者新的任務遇到I/O操作時,CPU再回到原來的任務繼續執行。
下圖(圖19-1)展示了兩個任務并發執行的過程(為了容易理解,這里以兩個任務并發執行為例,當然一個CPU核心并不僅僅只能兩個任務并發):

雖然CPU在同一時刻只能執行一個任務,但是通過將CPU的使用權在恰當的時機分配給不同的任務,使得多個任務看起來是一起執行的(CPU的執行速度極快,多任務切換的時間也極短)。
至此關于并發的概念就講解完成了。
19.1.2并行
并發是針對單核CPU提出的,而并行則是針對多核CPU提出的。和單核CPU不同,多核CPU真正實現了“同時執行多個任務”。多核CPU的每個核心都可以獨立地執行一個任務,而且多個核心之間不會相互干擾。在不同核心上執行的多個任務,是真正地同時運行,這種狀態就叫做并行。雙核CPU的工作狀態如下圖(圖19-2)所示:

雙核CPU執行兩個任務時,每個核心各自執行一個任務,和單核CPU在兩個任務之間不斷切換相比,它的執行效率更高。
至此對于并行的概念就講解完成了。
19.1.3并發+并行
在并行的工作狀態中,兩個CPU分別執行兩個任務,是一種理想狀態。但是在實際場景中,處于運行狀態的任務是非常多的,以實際辦公電腦為例,windows系統在開機之后會運行幾十個任務,而CPU往往只有4核、8核等,遠遠低于任務的數量,這個時候就會同時存在并發和并行兩種情況,即所有核心在并行工作的同時,每個核心還要并發工作。
例如一個雙核 CPU要執行四個任務,它的工作狀態如下圖(圖19-3)所示:

為了容易理解,這里是以兩個任務并發執行為例,當然一個CPU核心并不僅僅只能兩個任務并發,并發任務的數量和操作系統的分配方式、以及每個任務的工作狀態有關系。
至此,對于并發+并行的概念講解就結束了。
并發可以看作是并行的理想狀態,為了便于講解和避免產生歧義,之后的章節無論是并發還是并行,都會統稱為并發。
19.1.4競爭
并發可能會造成多個程序同時訪問一個共享資源,這時候由并發同時訪問一個共享資源產生的問題就叫做競爭。
競爭產生的原因如下所示:
(1)多線程的并發訪問。由于Linux是多任務操作系統,所以多線程訪問是競爭產生的基本原因。
(2)中斷程序的并發訪問。中斷任務產生后,CPU會立刻停止當前工作,從而去執行中斷中的任務,如果中斷任務對共享資源進行了修改,就會產生競爭。
(3)搶占式并發訪問。linux2.6及更高版本引入了搶占式內核,高優先級的任務可以打斷低優先級的任務。在線程訪問共享資源的時候,另一個線程打斷了現在正在訪問共享資源的線程同時也對共享資源進行操作,從而造成了競爭。
(4)多處理器(SMP)并發訪問。多核處理器之間存在核間并發訪問。
19.1.5共享資源的保護
競爭是由并發訪問同一個共享資源產生的。為了防止“競爭”的產生就要對共享資源進行保護,這里提到的共享資源又是什么呢?
以實際生活中的共享資源為例,可以是公共電話,也可以是共享單車、共享充電寶等公共物品,以上都屬于共享資源的范疇,以公共電話為例,每個人都可以對它進行使用,但在同一時間內只能由一個人進行使用,如果兩個人都要對電話進行使用,則產生了競爭。而在實際的驅動的代碼中,共享資源可以是全局變量,也可以是驅動中的設備結構體等,需要根據具體的驅動程序來進行分析。在下一小節的實驗中,會以全局變量為例,進行并發與競爭實驗。
19.2實驗程序的編寫
19.2.1驅動程序編寫
本實驗對應的網盤路徑為:iTOP-RK3568開發板【底板V1.7版本】\03_【iTOP-RK3568開發板】指南教程\02_Linux驅動配套資料\04_Linux驅動例程\14\module。
本實驗將編寫并發與競爭的驅動代碼,首先完善字符設備驅動框架,然后通過copy_from_user(…)函數接收用戶空間傳遞到內核空間的數據并進行判斷,如果接收到的字符串數據為“topeet”會在睡眠4秒鐘后打印接收到的數據,如果接收到的字符串數據為“itop”會在睡眠2秒鐘后打印接收到的數據。
編寫完成的example.c代碼如下所示
#include
#include
#include
#include
#include
#include
#include
static int open_test(struct inode *inode,struct file *file)
{
printk("\nthis is open_test \n");
return 0;
}
static ssize_t read_test(struct file *file,char __user *ubuf,size_t len,loff_t *off)
{
int ret;
char kbuf[10] = "topeet";//定義char類型字符串變量kbuf
printk("\nthis is read_test \n");
ret = copy_to_user(ubuf,kbuf,strlen(kbuf));//使用copy_to_user接收用戶空間傳遞的數據
if (ret != 0){
printk("copy_to_user is error \n");
}
printk("copy_to_user is ok \n");
return 0;
}
static char kbuf[10] = {0};//定義char類型字符串全局變量kbuf
static ssize_t write_test(struct file *file,const char __user *ubuf,size_t len,loff_t *off)
{
int ret;
ret = copy_from_user(kbuf,ubuf,len);//使用copy_from_user接收用戶空間傳遞的數據
if (ret != 0){
printk("copy_from_user is error\n");
}
if(strcmp(kbuf,"topeet") == 0 ){//如果傳遞的kbuf是topeet就睡眠四秒鐘
ssleep(4);
}
else if(strcmp(kbuf,"itop") == 0){//如果傳遞的kbuf是itop就睡眠兩秒鐘
ssleep(2);
}
printk("copy_from_user buf is %s \n",kbuf);
return 0;
}
static int release_test(struct inode *inode,struct file *file)
{
//printk("\nthis is release_test \n");
return 0;
}
struct chrdev_test {
dev_t dev_num;//定義dev_t類型變量dev_num來表示設備號
int major,minor;//定義int類型的主設備號major和次設備號minor
struct cdev cdev_test;//定義struct cdev類型結構體變量cdev_test,表示要注冊的字符設備
struct class *class_test;//定于struct class *類型結構體變量class_test,表示要創建的類
};
struct chrdev_test dev1;//創建chrdev_test類型的
struct file_operations fops_test = {
.owner = THIS_MODULE,//將owner字段指向本模塊,可以避免在模塊的操作正在被使用時卸載該模塊
.open = open_test,//將open字段指向open_test(...)函數
.read = read_test,//將read字段指向read_test(...)函數
.write = write_test,//將write字段指向write_test(...)函數
.release = release_test,//將release字段指向release_test(...)函數
};
static int __init atomic_init(void)
{
if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自動獲取設備號,設備名chrdev_name
printk("alloc_chrdev_region is error \n");
}
printk("alloc_chrdev_region is ok \n");
dev1.major = MAJOR(dev1.dev_num);//使用MAJOR()函數獲取主設備號
dev1.minor = MINOR(dev1.dev_num);//使用MINOR()函數獲取次設備號
printk("major is %d,minor is %d\n",dev1.major,dev1.minor);
cdev_init(&dev1.cdev_test,&fops_test);//使用cdev_init()函數初始化cdev_test結構體,并鏈接到fops_test結構體
dev1.cdev_test.owner = THIS_MODULE;//將owner字段指向本模塊,可以避免在模塊的操作正在被使用時卸載該模塊
cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用cdev_add()函數進行字符設備的添加
dev1.class_test = class_create(THIS_MODULE,"class_test");//使用class_create進行類的創建,類名稱為class_test
device_create(dev1.class_test,0,dev1.dev_num,0,"device_test");//使用device_create進行設備的創建,設備名稱為device_test
return 0;
}
static void __exit atomic_exit(void)
{
device_destroy(dev1.class_test,dev1.dev_num);//刪除創建的設備
class_destroy(dev1.class_test);//刪除創建的類
cdev_del(&dev1.cdev_test);//刪除添加的字符設備cdev_test
unregister_chrdev_region(dev1.dev_num,1);//釋放字符設備所申請的設備號
printk("module exit \n");
}
module_init(atomic_init);
module_exit(atomic_exit)
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");
對于重要邏輯部分已經加粗,后續章節的實驗都是對上述并發與競爭實驗的改進,以不同的方式來避免競爭的產生。
19.2.2編寫測試APP
本實驗應用程序對應的網盤路徑為:iTOP-RK3568開發板【底板V1.7版本】\03_【iTOP-RK3568開發板】指南教程\02_Linux驅動配套資料\04_Linux驅動例程\14\app。
本測試app較為簡單,需要輸入兩個參數,第一個參數為對應的設備節點,第二個參數為“topeet”或者“itop”,分別代表向設備寫入的數據,編寫完成的應用程序app.c內容如下所示:
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;//定義int類型的文件描述符
char str1[10] = {0};//定義讀取緩沖區str1
fd = open(argv[1],O_RDWR);//調用open函數,打開輸入的第一個參數文件,權限為可讀可寫
if(fd < 0 ){
printf("file open failed \n");
return -1;
}
/*如果第二個參數為topeet,條件成立,調用write函數,寫入topeet*/
if (strcmp(argv[2],"topeet") == 0 ){
write(fd,"topeet",10);
}
/*如果第二個參數為itop,條件成立,調用write函數,寫入itop*/
else if (strcmp(argv[2],"itop") == 0 ){
write(fd,"itop",10);
}
close(fd);
return 0;
}
19.3運行測試
19.3.1編譯驅動程序
在上一小節中的example.c代碼同一目錄下創建Makefile文件,Makefile文件內容如下所示:
export ARCH=arm64#設置平臺架構
export CROSS_COMPILE=aarch64-linux-gnu-#交叉編譯器前綴
obj-m += example.o #此處要和你的驅動源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel #這里是你的內核目錄
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操作
對于Makefile的內容注釋已在上圖添加,保存退出之后,來到存放example.c和Makefile文件目錄下,如下圖所示:

然后使用命令“make”進行驅動的編譯,編譯完成如下圖(圖19-5)所示:

編譯完生成example.ko目標文件,如下圖(圖19-6)所示:

至此驅動模塊就編譯成功了,下面進行應用程序的編譯。
19.3.2編譯應用程序
來到應用程序app.c文件的存放路徑如下圖(圖19-7)所示:

然后使用以下命令對app.c進行交叉編譯,編譯完成如下圖(圖19-8)所示:
1
aarch64-linux-gnu-gcc -o app app.c -static

生成的app文件就是之后放在開發板上運行的可執行文件,至此應用程序的編譯就完成了。
19.3.3運行測試
開發板啟動之后,使用以下命令進行驅動模塊的加載,如下圖(圖19-9)所示:
1
insmod example.ko

可以看到申請的主設備號和次設備號就被打印了出來,然后使用以下代碼對自動生成的設備節點device_test進行查看,如下圖(圖19-10)所示:
1
ls /dev/device_test

可以看到device_test節點已經被自動創建了,然后使用以下命令運行測試app,運行結果如下圖(圖19-11)所示:
1
./app /dev/device_test topeet

可以看到傳遞的buf值為topeet,然后輸入以下命令在后臺運行兩個app,來進行競爭測試,運行結果如下圖(圖19-12)所示:
./app /dev/device_test topeet &
./app /dev/device_test itop &

在不存在競爭的情況下,傳遞的兩個字符串數據應該是topeet和itop,而在上圖中的打印信息為兩個itop,原因是第二個app應用程序運行之后對共享資源進行了修改,兩個app應用程序就產生了競爭關系,會在之后的章節中使用不同的方法對上述驅動程序進行改進,從而避免競爭的產生。
最后可以使用以下命令進行驅動的卸載,如下圖(圖19-13)所示:
1
rmmod example.ko

至此,并發與競爭的實驗就完成了。
-
驅動
+關注
關注
12文章
1878瀏覽量
86321 -
開發板
+關注
關注
25文章
5378瀏覽量
100678 -
RK3568
+關注
關注
5文章
559瀏覽量
5902
發布評論請先 登錄
相關推薦
rk3568硬件開發筆記(第三篇 ) 網絡設計
iTOP-RK3568開發板驅動指南第五篇-中斷
更新 | 持續開源 迅為RK3568驅動指南第十一篇-pinctrl子系統
i.MX6ULL|并發與競爭實驗

技術分享 | RK3568編譯第三方驅動

【北京迅為】iTOP-RK3568開發板OpenHarmony系統南向驅動開發-第4章 UART基礎知識

評論