【系列文章】Linux中的并發與競爭[04]-信號量

【系列文章】Linux中的并發與競爭[04]-信號量

該文章為系列文章:Linux中的并發與競爭中的第4篇
該系列的導航頁連接:
【系列文章】Linux中的并發與競爭-導航頁


文章目錄

  • 【系列文章】Linux中的并發與競爭[04]-信號量
    • 一、信號量
    • 二、實驗程序的編寫
      • 2.1驅動程序編寫
      • 2.2編寫測試 APP
      • 2.3運行測試


在上面兩篇文章對自旋鎖和自旋鎖死鎖進行了學習,自旋鎖會讓請求的任務原地“自旋”,在等待的過程中會循環檢測自旋鎖的狀態,進而占用系統資源,而本章節要講解的信號量也是解決競爭的一種常用方法,與自旋鎖不同的是,信號量會使等待的線程進入休眠狀態,適用于那些占用資源比較久的場合。下面對信號量相關知識的進行講解。

一、信號量

信號量是操作系統中最典型的用于同步和互斥的手段,本質上是一個全局變量,信號量的值表示控制訪問資源的線程數,可以根據實際情況來自行設置,如果在初始化的時候將信號量量值設置為大于 1,那么這個信號量就是計數型信號量,允許多個線程同時訪問共享資源。如果將信號量量值設置為 1,那么這個信號量就是二值信號量,同一時間內只允許一個線程訪問共享資源,注意!信號量的值不能小于 0。當信號量的值為 0 時,想訪問共享資源的線程必須等待,直到信號量大于 0 時,等待的線程才可以訪問。當訪問共享資源時,信號量執行“減一”操作,訪問完成后再執行“加一”操作。

相比于自旋鎖,信號量具有休眠特性,因此適用長時間占用資源的場合,但由于信號量會引起休眠,所以不能用在中斷函數中,最后如果共享資源的持有時間比較短,使用信號量的話會造成頻繁的休眠,反而帶來更多資源的消耗,使用自旋鎖反而效果更好。再同時使用信號量和自旋鎖的時候,要先獲取信號量,再使用自旋鎖,因為信號量會導致睡眠。

以現實生活中的銀行辦理業務為例,銀行的業務辦理窗口就是共享資源,業務辦理窗口的數量就是信號量量值,進入銀行之后,客戶需要領取相應的排序碼,然后在休息區進行等待,可以看作線程的睡眠階段,當前面的客戶辦理完業務之后,相應的窗口會空閑出來,可以看作信號量的釋放,之后銀行會通過廣播,提醒下一位客戶到指定的窗口進行業務的辦理,可以看作線程的喚醒并獲取到信號量,訪問共享資源的過程。

Linux 內核使用 semaphore 結構體來表示信號量,該結構體定義在“內核源碼/include/linux/semaphore.h”文件內,結構體內容如下所示:

struct semaphore {raw_spinlock_t lock;unsigned int count;struct list_head wait_list;
};

與信號量相關的 API 函數同樣定義在 semaphore.h 文件內,部分常用 API 函數如下:

函數描述
DEFINE_SEAMPHORE(name)定義信號量,并且設置信號量的值為1。
void sema_init(struct semaphore *sem, int val)初始化信號量 sem,設置信號量值為 val。
void down(struct semaphore *sem)獲取信號量,不能被中斷打斷,如ctrl+c
int down_interruptible(struct semaphore *sem)獲取信號量,可以被中斷打斷,如ctrl+c
void up(struct semaphore *sem)釋放信號量
int down_trylock(struct semaphore *sem);嘗試獲取信號量,如果能獲取到信號量就獲取,并且返回0。 如果不能就返回非0

二、實驗程序的編寫

2.1驅動程序編寫

與之前章節設置標志位,在同一時間內只允許一個任務對共享資源進行訪問的方式所不同,本小節將采用信號量的方式避免競爭的產生。本實驗設置的信號量量值為 1,所以需要在open()函數中加入信號量獲取函數,在 release()函數中加入信號量釋放函數即可。

編寫完成的 semaphore.c 代碼如下所示

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/semaphore.h>struct semaphore semaphore_test;//定義一個 semaphore 類型的結構體變量 semaphore_teststatic int open_test(struct inode *inode,struct file *file)
{printk("\nthis is open_test \n");down(&semaphore_test);//信號量數量減 1return 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 類型字符串變量 kbufprintk("\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 類型字符串全局變量 kbufstatic 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)
{up(&semaphore_test);//信號量數量加 1printk("\nthis is release_test \n");return 0;
}struct chrdev_test {dev_t dev_num;//定義 dev_t 類型變量 dev_num 來表示設備號int major,minor;//定義 int 類型的主設備號 major 和次設備號 minorstruct 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)
{sema_init(&semaphore_test,1);//初始化信號量結構體 semaphore_test,并設置信號量的數量為 1if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自動獲取設備號,設備名chrdev_nameprintk("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()函數初始化 cdev_test 結構體,并鏈接到fops_test 結構體cdev_init(&dev1.cdev_test,&fops_test);//將 owner 字段指向本模塊,可以避免在模塊的操作正在被使用時卸載該模塊dev1.cdev_test.owner = THIS_MODULE;cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用 cdev_add()函數進行字符設備的添加//使用 class_create 進行類的創建,類名稱為class_testdev1.class_test = class_create(THIS_MODULE,"class_test");//使用 device_create 進行設備的創建,設備名稱為 device_testdevice_create(dev1.class_test,0,dev1.dev_num,0,"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_testunregister_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");

2.2編寫測試 APP

#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};//定義讀取緩沖區 str1fd = 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;
}

2.3運行測試

使用以下命令運行測試 app

./app /dev/device_test topeet

在這里插入圖片描述
可以看到傳遞的 buf 值為 topeet,然后輸入以下命令在后臺運行兩個 app,來進行競爭測試,運行結果如下圖所示:

./app /dev/device_test topeet &
./app /dev/device_test itop

在這里插入圖片描述
上述打印信息正常,證明數據被正確傳遞了,沒有發生共享資源的競爭,第一個任務運行之后,由于設置的信號量量值為 1,所以第二個任務會進入休眠狀態,第一個任務執行完畢之后,會喚醒第二個任務去執行,所以避免了并發與競爭。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/922426.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/922426.shtml
英文地址,請注明出處:http://en.pswp.cn/news/922426.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Elasticsearch啟動失敗?5步修復權限問題

文章目錄&#x1f6a8; 為什么會出現這個問題&#xff1f;? 解決方案&#xff1a;修復數據目錄權限并確保配置生效步驟 1&#xff1a;確認數據目錄存在且權限正確步驟 2&#xff1a;確認 elasticsearch.yml 中的配置步驟 3&#xff1a;**刪除或清空 /usr/share/elasticsearch/…

Docker push 命令:鏡像發布與管理的藝術

Docker push 命令&#xff1a;鏡像發布與管理的藝術1. 命令概述2. 命令語法3. 核心參數解析4. 推送架構圖解5. 完整工作流程6. 實戰場景示例6.1 基礎推送操作6.2 企業級推送流程6.3 多架構鏡像推送7. 鏡像命名規范詳解8. 安全最佳實踐8.1 內容信任機制8.2 最小權限原則9. 性能優…

智能合約測試框架全解析

概述 智能合約測試庫是區塊鏈開發中至關重要的工具&#xff0c;用于確保智能合約的安全性、正確性和可靠性。以下是主流的智能合約測試庫及其詳細解析。 一、主流測試框架對比 測試框架開發語言主要特點適用場景Hardhat WaffleJavaScript/TypeScript強大的調試功能&#xf…

【大模型算法工程師面試題】大模型領域新興的主流庫有哪些?

文章目錄 大模型領域新興主流庫全解析:國產化適配+優劣對比+選型指南(附推薦指數) 引言 一、總覽:大模型工具鏈選型框架(含推薦指數) 二、分模塊詳解:優劣對比+推薦指數+選型建議 2.1:訓練框架(解決“千億模型怎么訓”) 2.2:推理優化(解決“模型跑起來慢”) 2.3:…

端口打開與服務可用

端口打開與服務可用“端口已打開但服務不可用” 并非矛盾&#xff0c;而是網絡訪問中常見的分層問題。要理解這一點&#xff0c;需要先明確 “端口打開” 和 “服務可用” 的本質區別&#xff1a;1. 什么是 “端口打開”&#xff1f;“端口打開” 通常指 操作系統的網絡層監聽該…

ByteDance_FrontEnd

約面了&#xff0c;放輕松&#xff0c;好好面 盲點 基礎知識 Function 和 Object 都是函數&#xff0c;而函數也是對象。 Object.prototype 是幾乎所有對象的原型鏈終點&#xff08;其 proto 是 null&#xff09;。 Function.prototype 是所有函數的原型&#xff08;包括 Obje…

go語言,彩色驗證碼生成,加減法驗證,

代碼結構相關代碼 captcha/internal/captcha/generator.go package captchaimport (_ "embed" // &#x1f448; 啟用 embed"image""image/color""image/draw""image/png""io""math/rand""golang.…

PuTTY軟件訪問ZYNQ板卡的Linux系統

PuTTY 是一款非常經典、輕量級、免費的 SSH、Telnet 和串行端口連接客戶端&#xff0c;主要運行于 Windows 平臺。它是在開源許可下開發的&#xff0c;因其小巧、簡單、可靠而成為系統管理員、網絡工程師和開發人員的必備工具。網上有非常多的下載資源。 我們使用PuTTY軟件對ZY…

做一個RBAC權限

在分布式應用場景下&#xff0c;我們可以利用網關對請求進行集中處理&#xff0c;實現了低耦合&#xff0c;高內聚的特性。 登陸權限驗證和鑒權的功能都可以在網關層面進行處理&#xff1a; 用戶登錄后簽署的jwt保存在header中&#xff0c;用戶信息則保存在redis中網關應該對不…

【算法】day1 雙指針

1、移動零&#xff08;同向分3區域&#xff09; 283. 移動零 - 力扣&#xff08;LeetCode&#xff09; 題目&#xff1a; 思路&#xff1a;注意原地操作。快排也是這個方法&#xff1a;左邊小于等于 tmp&#xff0c;右邊大于 tmp&#xff0c;最后 tmp 放到 dest。 代碼&#…

Linux 日志分析:用 ELK 搭建個人運維監控平臺

Linux 日志分析&#xff1a;用 ELK 搭建個人運維監控平臺 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般絢爛的技術棧中&#xff0c;我是那個永不停歇的色彩收集者。 &#x1f98b; 每一個優化都是我培育的花朵&#xff0c;每一個特性都是我放飛…

Linux網絡:socket編程UDP

文章目錄前言一&#xff0c;socket二&#xff0c;服務端socket3-1 創建socket3-2 綁定地址和端口3-3 接收數據3-4 回復數據3-5關閉socket3-6 完整代碼三&#xff0c;客戶端socket3-1 為什么客戶端通常不需要手動定義 IP 和端口前言 學習 socket 編程的意義在于&#xff1a;它讓…

【從零到公網】本地電腦部署服務并實現公網訪問(IPv4/IPv6/DDNS 全攻略)

從零到公網&#xff1a;本地電腦部署服務并實現公網訪問&#xff08;IPv4/IPv6/DDNS 全攻略&#xff09; 適用場景&#xff1a;本地 API 服務、大模型推理服務、NAS、遠程桌面等需要公網訪問的場景 關鍵詞&#xff1a;公網 IP、端口映射、內網穿透、IPv6、Cloudflare DDNS 一、…

模塊二 落地微服務

11 | 服務發布和引用的實踐 服務發布和引用常見的三種方式&#xff1a;Restful API、XML配置以及IDL文件。今天我將以XML配置方式為例&#xff0c;給你講解服務發布和引用的具體實踐以及可能會遇到的問題。 XML配置方式的服務發布和引用流程 1. 服務提供者定義接口 服務提供者發…

C++程序員速通C#:從Hello World到數據類型

C程序員光速入門C#&#xff08;一&#xff09;&#xff1a;總覽、數據類型、運算符 一.Hello world&#xff01; 隨著.NET的深入人心,作為一個程序員&#xff0c;當然不能在新技術面前停而止步&#xff0c;面對著c在.net中的失敗,雖然有一絲遺憾&#xff0c;但是我們應該認識到…

Linux相關概念和易錯知識點(44)(IP地址、子網和公網、NAPT、代理)

目錄1.IP地址&#xff08;1&#xff09;局域網和公網①局域網a.網關地址b.局域網通信②運營商子網③公網&#xff08;2&#xff09;NAPT①NAPT過程②理解NAPT③理解源IP和目的IPa.目的IPb.源IP③最長前綴匹配④NAT技術缺陷2.代理服務&#xff08;1&#xff09;正向代理&#xf…

工業智能終端賦能自動化生產線建設數字化管理

在當今數字化浪潮的推動下&#xff0c;自動化生產線正逐漸成為各行各業提升效率和降低成本的重要選擇。隨著智能制造的深入發展&#xff0c;工業智能終端的引入不僅為生產線帶來了技術革新&#xff0c;也賦予了數字化管理新的動力。一、工業智能終端&#xff1a;一體化設計&…

【Vue2手錄06】計算屬性Computed

一、表單元素的v-model綁定&#xff08;核心場景&#xff09; v-model 是Vue實現“表單元素與數據雙向同步”的語法糖&#xff0c;不同表單元素的綁定規則存在差異&#xff0c;需根據元素類型選擇正確的綁定方式。 1.1 四大表單元素的綁定規則對比表單元素類型綁定數據類型核心…

FPGA入門-數碼管靜態顯示

19. 數碼管的靜態顯示 在許多項目設計中&#xff0c;我們通常需要一些顯示設備來顯示我們需要的信息&#xff0c;可以選擇的顯示設備有很多&#xff0c;而數碼管是使用最多&#xff0c;最簡單的顯示設備之一。數碼管是一種半導體發光器件&#xff0c;具有響應時間短、體積小、…

深入理解大語言模型(5)-關于token

到目前為止對 LLM 的描述中&#xff0c;我們將其描述為一次預測一個單詞&#xff0c;但實際上還有一個更重要的技術細 節。即 LLM 實際上并不是重復預測下一個單詞&#xff0c;而是重復預測下一個 token 。對于一個句子&#xff0c;語言模型會 先使用分詞器將其拆分為一個個 to…