瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工藝,搭載一顆四核Cortex-A55處理器和Mali G52 2EE 圖形處理器。RK3568 支持4K 解碼和 1080P 編碼,支持SATA/PCIE/USB3.0 外圍接口。RK3568內(nèi)置獨立NPU,可用于輕量級人工智能應(yīng)用。RK3568 支持安卓 11 和 linux 系統(tǒng),主要面向物聯(lián)網(wǎng)網(wǎng)關(guān)、NVR 存儲、工控平板、工業(yè)檢測、工控盒、卡拉 OK、云終端、車載中控等行業(yè)。
?
【公眾號】迅為電子
【粉絲群】824412014(加群獲取驅(qū)動文檔+例程)
【視頻觀看】嵌入式學(xué)習(xí)之Linux驅(qū)動(第三篇-并發(fā)與競爭_全新升級)_基于RK3568
【購買鏈接】迅為RK3568開發(fā)板瑞芯微Linux安卓鴻蒙ARM核心板人工智能AI主板
在上一小節(jié)中,學(xué)習(xí)了內(nèi)核中自旋鎖的使用,而自旋鎖若是使用不當(dāng)就會產(chǎn)生死鎖,在本章將會對自旋鎖的特殊情況-死鎖進行講解。
第22章 自旋鎖死鎖實驗
22.1 自旋鎖死鎖
死鎖是指兩個或多個事物在同一資源上相互占用,并請求鎖定對方的資源,從而導(dǎo)致惡性循環(huán)的現(xiàn)象。當(dāng)多個進程因競爭資源而造成的一種僵局(互相等待),若無外力作用,這些進程都將無法向前推進,這種情況就是死鎖。
自旋鎖死鎖發(fā)生存在兩種情況:
(1)第一種情況是擁有自旋鎖的進程A在內(nèi)核態(tài)阻塞了,內(nèi)核調(diào)度B進程,碰巧B進程也要獲得自旋鎖,此時B只能自旋轉(zhuǎn)。而此時搶占已經(jīng)關(guān)閉(在單核條件下)不會調(diào)度A進程了,B永遠自旋,產(chǎn)生死鎖,如下圖(圖 22-1)所示:
相應(yīng)的解決辦法是,在自旋鎖的使用過程中要盡可能短的時間內(nèi)擁有自旋鎖,而且不能在臨界區(qū)中調(diào)用導(dǎo)致線程休眠的函數(shù)。
第二種情況是進程A擁有自旋鎖,中斷到來,CPU執(zhí)行中斷函數(shù),中斷處理函數(shù),中斷處理函數(shù)需要獲得自旋鎖,訪問共享資源,此時無法獲得鎖,只能自旋,從而產(chǎn)生死鎖,如下圖(圖22-2)所示:
對于中斷引發(fā)的死鎖,最好的解決方法就是在獲取鎖之前關(guān)閉本地中斷,Linux內(nèi)核在“/include/linux/spinlock.h”文件中提供了相應(yīng)的API 函數(shù),如下(圖22-3)所示:
函數(shù) | 描述 |
---|---|
void spin_lock_irq(spinlock_t *lock) | 禁止本地中斷,并獲取自旋鎖。 |
void spin_unlock_irq(spinlock_t *lock) | 激活本地中斷,并釋放自旋鎖。 |
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags) | 恢復(fù)中斷狀態(tài),關(guān)閉中斷并獲取自旋鎖。 |
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) | 將中斷狀態(tài)恢復(fù)到以前的狀態(tài),打開中斷并釋放自旋鎖 |
void spin_lock_bh(spinlock_t *lock) | 關(guān)閉下半部,獲取自旋鎖 |
void spin_unlock_bh(spinlock_t *lock) | 打開下半部,獲取自旋鎖 |
由于Linux內(nèi)核運行是非常復(fù)雜的,很難確定某個時刻的中斷狀態(tài),因此建議使用 spin_lock_irqsave/spin_unlock_irqrestore,因為這一組函數(shù)會保存中斷狀態(tài),在釋放鎖的時候會恢復(fù)中斷狀態(tài)。
在下一小節(jié)中將進行自旋鎖死鎖實驗,本次實驗所采取的是第一種情況,即擁有自旋鎖的進程A在內(nèi)核態(tài)阻塞了,內(nèi)核調(diào)度B進程,碰巧B進程也要獲得自旋鎖,依次產(chǎn)生死鎖。
22.2 實驗程序的編寫
22.2.1 驅(qū)動程序編寫
本實驗對應(yīng)的網(wǎng)盤路徑為:iTOP-RK3568開發(fā)板【底板V1.7版本】\03_【iTOP-RK3568開發(fā)板】指南教程\02_Linux驅(qū)動配套資料\04_Linux驅(qū)動例程\17\module。
本章節(jié)實驗以19章并發(fā)與競爭實驗為基礎(chǔ),在open()函數(shù)中加入了自旋鎖加鎖,在close()函數(shù)中加入了自旋鎖解鎖,由于在write()函數(shù)中存在sleep()睡眠函數(shù),所以會造成內(nèi)核阻塞,睡眠期間如果使用另一個進程獲取該自旋鎖,就會造成死鎖。
編寫完成的dielock.c代碼如下所示
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/spinlock.h>
static spinlock_t spinlock_test;//定義spinlock_t類型的自旋鎖變量spinlock_test
static int open_test(struct inode *inode,struct file *file)
{
//printk("\nthis is open_test \n");
spin_lock(&spinlock_test);//自旋鎖加鎖
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接收用戶空間傳遞的數(shù)據(jù)
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接收用戶空間傳遞的數(shù)據(jù)
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");
spin_unlock(&spinlock_test);//自旋鎖解鎖
return 0;
}
struct chrdev_test {
dev_t dev_num;//定義dev_t類型變量dev_num來表示設(shè)備號
int major,minor;//定義int類型的主設(shè)備號major和次設(shè)備號minor
struct cdev cdev_test;//定義struct cdev 類型結(jié)構(gòu)體變量cdev_test,表示要注冊的字符設(shè)備
struct class *class_test;//定于struct class *類型結(jié)構(gòu)體變量class_test,表示要創(chuàng)建的類
};
struct chrdev_test dev1;//創(chuàng)建chrdev_test類型的
struct file_operations fops_test = {
.owner = THIS_MODULE,//將owner字段指向本模塊,可以避免在模塊的操作正在被使用時卸載該模塊
.open = open_test,//將open字段指向open_test(...)函數(shù)
.read = read_test,//將read字段指向read_test(...)函數(shù)
.write = write_test,//將write字段指向write_test(...)函數(shù)
.release = release_test,//將release字段指向release_test(...)函數(shù)
};
static int __init atomic_init(void)
{
spin_lock_init(&spinlock_test);
if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自動獲取設(shè)備號,設(shè)備名chrdev_name
printk("alloc_chrdev_region is error \n");
}
printk("alloc_chrdev_region is ok \n");
dev1.major = MAJOR(dev1.dev_num);//使用MAJOR()函數(shù)獲取主設(shè)備號
dev1.minor = MINOR(dev1.dev_num);//使用MINOR()函數(shù)獲取次設(shè)備號
printk("major is %d,minor is %d\n",dev1.major,dev1.minor);
cdev_init(&dev1.cdev_test,&fops_test);//使用cdev_init()函數(shù)初始化cdev_test結(jié)構(gòu)體,并鏈接到fops_test結(jié)構(gòu)體
dev1.cdev_test.owner = THIS_MODULE;//將owner字段指向本模塊,可以避免在模塊的操作正在被使用時卸載該模塊
cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用cdev_add()函數(shù)進行字符設(shè)備的添加
dev1.class_test = class_create(THIS_MODULE,"class_test");//使用class_create進行類的創(chuàng)建,類名稱為class_test
device_create(dev1.class_test,0,dev1.dev_num,0,"device_test");//使device_create進行設(shè)備的創(chuàng)建,設(shè)備名稱為device_test
return 0;
}
static void __exit atomic_exit(void)
{
device_destroy(dev1.class_test,dev1.dev_num);//刪除創(chuàng)建的設(shè)備
class_destroy(dev1.class_test);//刪除創(chuàng)建的類
cdev_del(&dev1.cdev_test);//刪除添加的字符設(shè)備cdev_test
unregister_chrdev_region(dev1.dev_num,1);//釋放字符設(shè)備所申請的設(shè)備號
printk("module exit \n");
}
Module_init(atomic_init);
module_exit(atomic_exit)
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");
22.2.2 編寫測試 APP
本實驗應(yīng)用程序?qū)?yīng)的網(wǎng)盤路徑為:iTOP-RK3568開發(fā)板【底板V1.7版本】\03_【iTOP-RK3568開發(fā)板】指南教程\02_Linux驅(qū)動配套資料\04_Linux驅(qū)動例程\17\app。
本測試app代碼和上一章節(jié)相同,需要輸入兩個參數(shù),第一個參數(shù)為對應(yīng)的設(shè)備節(jié)點,第二個參數(shù)為“topeet”或者“itop”,分別代表向設(shè)備寫入的數(shù)據(jù),編寫完成的應(yīng)用程序app.c內(nèi)容如下所示:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;//定義int類型的文件描述符
char str1[10] = {0};//定義讀取緩沖區(qū)str1
fd = open(argv[1],O_RDWR);//調(diào)用open函數(shù),打開輸入的第一個參數(shù)文件,權(quán)限為可讀可寫
if(fd < 0 ){
printf("file open failed \n");
return -1;
}
/*如果第二個參數(shù)為topeet,條件成立,調(diào)用write函數(shù),寫入topeet*/
if (strcmp(argv[2],"topeet") == 0 ){
write(fd,"topeet",10);
}
/*如果第二個參數(shù)為itop,條件成立,調(diào)用write函數(shù),寫入itop*/
else if (strcmp(argv[2],"itop") == 0 ){
write(fd,"itop",10);
}
close(fd);
return 0;
}
由于本次測試的CPU為多核心CPU,其他核心仍舊可以調(diào)度其他進程,所以需要多次使用taskset函數(shù)指定CPU進行進程的運行,以此來產(chǎn)生死鎖,在與app.c同級目錄下創(chuàng)建名為app.sh的腳本文件,腳本內(nèi)容如下所示:
#!/bin/bash
taskset -c 0 ./app /dev/device_test topeet &
taskset -c 1 ./app /dev/device_test topeet &
taskset -c 2 ./app /dev/device_test topeet &
taskset -c 3 ./app /dev/device_test topeet &
taskset -c 0 ./app /dev/device_test topeet &
taskset -c 1 ./app /dev/device_test topeet &
taskset -c 2 ./app /dev/device_test topeet &
保存退出之后,需要使用以下命令賦予腳本可執(zhí)行權(quán)限,如下圖(圖22-4)所示:
chmod 777 app.sh
至此測試程序app.c和運行腳本app.sh就編寫完成了。
22.3 運行測試
22.3.1 編譯驅(qū)動程序
在上一小節(jié)中的dielock.c代碼同一目錄下創(chuàng)建 Makefile 文件,Makefile 文件內(nèi)容如下所示:
export ARCH=arm64#設(shè)置平臺架構(gòu)
export CROSS_COMPILE=aarch64-linux-gnu-#交叉編譯器前綴
obj-m += dielock.o #此處要和你的驅(qū)動源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel #這里是你的內(nèi)核目錄
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操作
對于Makefile的內(nèi)容注釋已在上圖添加,保存退出之后,來到存放dielock.c和Makefile文件目錄下,如下圖(圖22-5)所示:
然后使用命令“make”進行驅(qū)動的編譯,編譯完成如下圖(圖22-6)所示:
編譯完生成dielock.ko目標(biāo)文件,如下圖(圖22-7)所示:
至此驅(qū)動模塊就編譯成功了,下面進行應(yīng)用程序的編譯。
22.3.2 編譯應(yīng)用程序
來到應(yīng)用程序app.c文件的存放路徑如下圖(圖22-8)所示:
然后使用以下命令對app.c進行交叉編譯,編譯完成如下圖(圖22-9)所示:
aarch64-linux-gnu-gcc -o app app.c -static
生成的app文件就是之后放在開發(fā)板上運行的可執(zhí)行文件,至此應(yīng)用程序的編譯就完成了。
22.3.3 運行測試
開發(fā)板啟動之后,使用以下命令進行驅(qū)動模塊的加載,如下圖(圖22-10)所示:
insmod dielock.ko
可以看到申請的主設(shè)備號和次設(shè)備號就被打印了出來,然后使用以下代碼對自動生成的設(shè)備節(jié)點device_test進行查看,如下圖(圖22-11)所示:
ls /dev/device_test
可以看到device_test節(jié)點已經(jīng)被自動創(chuàng)建了,然后使用以下命令運行app.sh腳本,該腳本會指定CPU在加鎖之后進入內(nèi)核休眠狀態(tài),如下圖(圖22-12)所示:
./app.sh
? 在指令輸入之后,串口終端無法輸入,引發(fā)了死鎖,進而造成了系統(tǒng)崩潰,所以在編寫驅(qū)動的過程中,要盡可能的避免死鎖的出現(xiàn)。文章來源:http://www.zghlxwxcb.cn/news/detail-703042.html
至此,自旋鎖死鎖驅(qū)動實驗就完成了。文章來源地址http://www.zghlxwxcb.cn/news/detail-703042.html
到了這里,關(guān)于第22章 自旋鎖死鎖實驗(iTOP-RK3568開發(fā)板驅(qū)動開發(fā)指南 )的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!