Linux驅動開發進階(六)- 多線程與并發

文章目錄

  • 1、前言
  • 2、進程與線程
  • 3、內核線程
  • 4、底半步機制
    • 4.1、軟中斷
    • 4.2、tasklet
    • 4.3、工作隊列
      • 4.3.1、普通工作項
      • 4.3.2、延時工作項
      • 4.3.3、工作隊列
  • 5、中斷線程化
  • 6、進程
    • 6.1、內核進程
    • 6.2、用戶空間進程
  • 7、鎖機制
    • 7.1、原子操作
    • 7.2、自旋鎖
    • 7.3、信號量
    • 7.4、互斥鎖
    • 7.5、completion

1、前言

  1. 學習參考書籍以及本文涉及的示例程序:李山文的《Linux驅動開發進階》
  2. 本文屬于個人學習后的總結,不太具備教學功能。

2、進程與線程

略。

3、內核線程

在linux中,線程和進程實際上是同一個東西,本質就是為了完成任務。因此,linus將這個成為task,即任務。在內核中使用struct task_struct表示,包含了進程的各種信息,如進程ID、父進程指針、進程狀態、進程優先級、進程的內存管理信息等。

4、底半步機制

linux內核中,對于硬件中斷的處理,將中斷服務函數拆分為兩個部分,其中需要緊急處理的放在上半部分,主要處理一些與硬件以及關鍵數據結構相關的事情。將不那么緊急的事情放在下半部分。我們將上半部分稱之為頂半部,將下半部分稱之為底半部。

4.1、軟中斷

軟中斷一般很少直接用于實現下半部。軟中斷就是軟件實現的異步中斷,它的優先級比硬中斷低,但比普通進程優先級高,同時它和硬中斷一樣不能休眠。Linux內核中的軟中斷數組如下所示,用來記錄軟中斷的向量(軟中斷服務函數):

enum
{HI_SOFTIRQ=0,TIMER_SOFTIRQ,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,IRQ_POLL_SOFTIRQ,TASKLET_SOFTIRQ,SCHED_SOFTIRQ,HRTIMER_SOFTIRQ,RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */NR_SOFTIRQS
};

4.2、tasklet

tasklet依賴于軟中斷,內核使用一個鏈表的方式來管理所有的tasklet任務。tasklet的使用如下:先定義一個struct tasklet_struct結構體,然后使用tasklet_setup函數初始化(可能再比較老的內核版本是用tasklet_init()來初始化),最后使用tasklet_schedule函數來調度。

下面程序舉例如何使用tasklet。在按鍵中斷中發起tasklet調用。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/sysfs.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/poll.h>#define PIN_NUM    117 // gpio3_PC5struct gpio_key {dev_t          dev_num;struct cdev    cdev;struct class  *class;struct device *dev;struct tasklet_struct tasklet;
};static struct gpio_key *key;static irqreturn_t key_irq(int irq, void *args)
{tasklet_schedule(&key->tasklet);return IRQ_HANDLED;
}static int key_open (struct inode * inode, struct file * file)
{return 0;
}static int key_close(struct inode * inode, struct file * file)
{return 0;
}static struct file_operations key_ops = {.owner   = THIS_MODULE,.open    = key_open,.release = key_close,
};static void tasklet_handler(unsigned long data)
{printk(KERN_INFO "tasklet demo!\n");
}static int __init async_init(void)
{int ret, irq;key = kzalloc(sizeof(struct gpio_key), GFP_KERNEL);if(key == NULL) {printk(KERN_ERR "struct gpio_key alloc failed\n");return -ENOMEM;;}tasklet_init(&key->tasklet, tasklet_handler, 0);if (!gpio_is_valid(PIN_NUM)) {kfree(key);printk(KERN_ERR "gpio is invalid\n");return -EPROBE_DEFER;}ret = gpio_request(PIN_NUM, "key");if(ret) {kfree(key);printk(KERN_ERR "gpio request failed\n");return ret;}irq = gpio_to_irq(PIN_NUM);if (irq < 0) {printk(KERN_ERR "get gpio irq failed\n");goto err;}ret = request_irq(irq, key_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "key", key);if(ret) {printk(KERN_ERR "request irq failed\n");goto err;}ret = alloc_chrdev_region(&key->dev_num ,0, 1, "key");  //動態申請一個設備號if(ret !=0) {unregister_chrdev_region(key->dev_num, 1);printk(KERN_ERR "alloc_chrdev_region failed!\n");return -1;}key->cdev.owner = THIS_MODULE;cdev_init(&key->cdev, &key_ops);cdev_add(&key->cdev, key->dev_num, 1);key->class = class_create(THIS_MODULE, "key_class");if(key->class == NULL) {printk(KERN_ERR "key_class failed!\n");goto err1;}key->dev = device_create(key->class, NULL, key->dev_num, NULL, "key");if(IS_ERR(key->dev)) {printk(KERN_ERR "device_create failed!\n");goto err2;}return ret;
err2:class_destroy(key->class);
err1:unregister_chrdev_region(key->dev_num, 1);
err:gpio_free(PIN_NUM);kfree(key);return -1;
}static void __exit async_exit(void)
{//停止tasklet任務tasklet_disable(&key->tasklet);// 清理tasklet相關資源tasklet_kill(&key->tasklet);gpio_free(PIN_NUM);device_destroy(key->class, key->dev_num);class_destroy(key->class);unregister_chrdev_region(key->dev_num, 1);free_irq(gpio_to_irq(PIN_NUM), key);kfree(key);
}module_init(async_init);
module_exit(async_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("1477153217@qq.com");
MODULE_DESCRIPTION("async notify test");

4.3、工作隊列

實際tasklet還是適合處理較快的任務,因為tasklet不可被搶占,同時tasklet無法讓任務在多個核心上執行。

4.3.1、普通工作項

下面示例程序展示了如何使用工作隊列。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>            
#include <linux/device.h> 
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>//定義一個任務
struct task_struct *thread_worker;
//定義一個工作項
struct work_struct work;void work_func(struct work_struct *work)
{printk(KERN_INFO "work execute!\n");
}static int test_thread(void *data)
{while(!kthread_should_stop()) {schedule_work(&work);msleep(1000);}return 0;
}static int __init work_init(void)
{INIT_WORK(&work, work_func);//創建一個線程thread_worker = kthread_run(test_thread, NULL, "test_kthread");if (IS_ERR(thread_worker)) {return PTR_ERR(thread_worker);}return 0;
}static void __exit work_exit(void)
{kthread_stop(thread_worker);cancel_work_sync(&work);
}module_init(work_init);
module_exit(work_exit);MODULE_LICENSE("GPL");        
MODULE_AUTHOR("1477153217@qq.com");  
MODULE_VERSION("0.1");      
MODULE_DESCRIPTION("work demo");

4.3.2、延時工作項

即延時一段時間再執行。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>            
#include <linux/device.h> 
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>//定義一個任務
struct task_struct *thread_worker;
//定義一個工作項
struct work_struct work;void work_func(struct work_struct *work)
{printk(KERN_INFO "work execute!\n");
}static int test_thread(void *data)
{while(!kthread_should_stop()) {schedule_work(&work);msleep(1000);}return 0;
}static int __init work_init(void)
{INIT_WORK(&work, work_func);//創建一個線程thread_worker = kthread_run(test_thread, NULL, "test_kthread");if (IS_ERR(thread_worker)) {return PTR_ERR(thread_worker);}return 0;
}static void __exit work_exit(void)
{kthread_stop(thread_worker);cancel_work_sync(&work);
}module_init(work_init);
module_exit(work_exit);MODULE_LICENSE("GPL");        
MODULE_AUTHOR("1477153217@qq.com");  
MODULE_VERSION("0.1");      
MODULE_DESCRIPTION("work demo");

4.3.3、工作隊列

當有多個工作項時,可以放到工作隊列里。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>            
#include <linux/device.h> 
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>//定義一個任務隊列指針
static struct workqueue_struct *workqueue = NULL;
//定義一個任務
struct task_struct *thread_worker = NULL;
//定義一個工作項1
struct work_struct work1;
//定義一個工作項2
struct work_struct work2;void work1_func(struct work_struct *work)
{printk(KERN_INFO "work1 execute!\n");
}void work2_func(struct work_struct *work)
{printk(KERN_INFO "work2 execute!\n");
}static int test_thread(void *data)
{while(!kthread_should_stop()) {//將work1放到工作隊列中執行queue_work(workqueue,&work1);//將work2放到工作隊列中執行queue_work(workqueue,&work2);msleep(1000);}return 0;
}static int __init work_init(void)
{INIT_WORK(&work1, work1_func);INIT_WORK(&work2, work2_func);workqueue = create_singlethread_workqueue("wq_test");if(workqueue == NULL){return -1;}//創建一個線程thread_worker = kthread_run(test_thread, NULL, "test_kthread");if (IS_ERR(thread_worker)) {destroy_workqueue(workqueue);return PTR_ERR(thread_worker);}return 0;
}static void __exit work_exit(void)
{kthread_stop(thread_worker);destroy_workqueue(workqueue);cancel_work_sync(&work1);cancel_work_sync(&work2);
}module_init(work_init);
module_exit(work_exit);MODULE_LICENSE("GPL");        
MODULE_AUTHOR("1477153217@qq.com");  
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("work queue demo");

5、中斷線程化

上面我們介紹了底半部的一些機制,有軟中斷、tasklet、工作隊列。但為了進一步提高系統實時性,又將頂半步進一步拆分為硬件中斷處理和線程化中斷。(下圖來自李山文的《Linux驅動開發進階》)

硬件中斷處理:僅執行最緊急的任務(如讀取硬件寄存器、應答中斷)。仍然在中斷上下文中執行(不可睡眠,快速完成)。

線程化處理:剩余的頂半部邏輯移至一個專用的內核線程中執行。在進程上下文中運行(可睡眠,可被高優先級任務搶占)。

申請一個線程化中斷使用如下函數:

int request_threaded_irq(unsigned int irq, irq_handler_t handler,irq_handler_t thread_fn, unsigned long irqflags,const char *devname, void *dev_id)

下面是一個示例程序:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/sysfs.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>#define PIN_NUM    117 static int ev_press=0;static irqreturn_t key_irq(int irq, void *args)
{return IRQ_WAKE_THREAD;
}static irqreturn_t key_irq_thread(int irq, void *args)
{ev_press = 1; //按下按鍵printk(KERN_INFO "key press!\n");return IRQ_HANDLED;
}static int __init thread_irq_init(void)
{int ret, irq;if (!gpio_is_valid(PIN_NUM)) {printk(KERN_ERR "gpio is invalid\n");return -EPROBE_DEFER;}ret = gpio_request(PIN_NUM, "key");if(ret) {printk(KERN_ERR "gpio request failed\n");return -1;}irq = gpio_to_irq(PIN_NUM);if (irq < 0) {gpio_free(PIN_NUM);printk(KERN_ERR "get gpio irq failed\n");return -1;}ret = request_threaded_irq(irq, key_irq, key_irq_thread, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "key", &ev_press);if(ret) {gpio_free(PIN_NUM);printk(KERN_ERR "request irq failed\n");return -1;}return 0;
}static void __exit thread_irq_exit(void)
{gpio_free(PIN_NUM);free_irq(gpio_to_irq(PIN_NUM), &ev_press);
}module_init(thread_irq_init);
module_exit(thread_irq_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("1477153217@qq.com");
MODULE_DESCRIPTION("thread irq test");

6、進程

6.1、內核進程

引用書本原話:“Linux內核將所有的線程都當作進程來實現,每個線程都有一個唯一的task_struct(進程控制塊),在內核中看起來就像一個普通的進程,只是它與其他進程共享一些資源,如地址空間。所以從內核的角度來看,進程和線程沒有本質區別,只是在資源共享上有所不同。用戶空間可以使用clone、fork、vfork系統調用來創建進程,其最終調用的都是內核中的_do_fork函數。_do_fork函數調用copy_process函數來創建子進程的task_struct數據結構。”

(下圖來自李山文的《Linux驅動開發進階》)

6.2、用戶空間進程

在應用程序創建進程有如下函數:fork、vfork、clone、pthread_create。

在內核(kernel)層面,最終都會被表示為一個 task_struct 數據結構。

7、鎖機制

7.1、原子操作

在操作系統中一句C語言代碼是非常有可能被打斷的,為了防止這種情況發生,我們需要使用原子操作。

7.2、自旋鎖

自旋鎖就是不停的判斷一個鎖變量是否可用,如果可用,則繼續執行,否則一直等待。因此,自旋鎖適合用在一些任務頻繁調度的時候。自旋鎖還有一個特點是不可能引起睡眠,因此在中斷上下文中,必須使用自旋鎖來實現臨界區的訪問。

初始化一個自旋鎖:

spinlock_t lock;
spin_lock_init(&lock);

獲取鎖和釋放鎖:

spin_lock(&lock);
spin_unlock(&lock);

但使用自旋鎖時,如果產生了中斷,在中斷服務程序中也嘗試獲取鎖,那么就會產生死鎖,對于這種情況,應該先關閉中斷再獲取鎖,相關操作函數如下:

7.3、信號量

信號量是一個整型變量。P 操作用于申請資源,如果資源不可用(信號量 ≤ 0),則進程阻塞,直到資源可用。V 操作用于釋放資源,并喚醒等待的進程(如果有)。信號量是一種會導致進程睡眠的鎖機制,對于需要等待很長時間的進程而言,就需要采用信號量。

初始化一個信號量:

struct semaphore semap;
sema_init(&semap, 5);

PV操作相關的函數如下:

7.4、互斥鎖

互斥鎖在linux內核中使用較多,大部分情況下,都是對全局變量做保護。

初始化一個互斥鎖:

struct mutex tlb_lock;
mutex_init(&tlb_lock);

對互斥鎖上鎖和解鎖:

7.5、completion

當我們需要初始化一些東西,但在另一個線程必須等待這些初始化完成后才能繼續執行。為此linux提供了completion機制。

動態定義一個完成量:

struct completion setup_done;
init_completion(&setup_done);

在需要等待的地方調用wait_for_completion即可:

complete(&setup_done);
complete_all(&setup_done);

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

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

相關文章

第四節:React Hooks進階篇-useEffect依賴項為空數組[]與不寫的區別

陷阱題&#xff1a;閉包問題、Stale Closure舉例 一、依賴項為空數組[]與不寫的核心區別 行為空數組[]不寫依賴項執行時機僅在組件掛載時執行一次&#xff08;類似componentDidMount&#xff09;組件每次渲染后都執行&#xff08;類似componentDidUpdate&#xff09;更新觸發…

【第39節】windows編程:打造MFC版本任務管理器

目錄 一、項目概述 二、項目開發的各種功能關鍵 2.1 進程信息的獲取 2.2 線程信息的獲取 2.3 進程模塊信息的獲取 2.3.1 模塊快照 2.3.2 枚舉模塊 2.4 進程堆信息的獲取 2.5 窗口信息的獲取 2.6 文件信息的獲取 2.7 內存信息和CPU占用率的獲取 2.7.1 內存信息相關結…

計算軸承|滾動軸承故障頻率

一、軸承故障頻率概述 在旋轉機械故障診斷中&#xff0c;軸承故障頻率&#xff08;BPFO、BPFI、BSF、FTF&#xff09;是重要的分析依據。通過計算這些特征頻率&#xff0c;可以幫助工程師&#xff1a; 識別軸承故障類型&#xff08;內圈/外圈/滾動體故障&#xff09;制定振動…

【數據結構與算法】ArrayList 和 順序表

文章目錄 &#x1f332;List&#x1f332;1. 線性表&#x1f332;2. 順序表&#x1f33f;2.1 MyArrayList2.1.1 類中重寫所有接口方法1.新增元素2.在pos位置新增元素(指定位置)3.判定是否包含了某個特定元素 4.查找特定元素對應的位置 5.獲取pos下標的元素 6.給pos位置的元素替…

OceanBase 推出單機版 ,為中小規模業務提供高性價比方案

近日&#xff0c;OceanBase正式推出了全新的單機版數據庫。這款產品基于OceanBase自主研發的單機分布式一體化架構&#xff0c;具有精簡的架構設計和出色的兼容性&#xff0c;能夠為中小規模業務場景提供高性價比的數據庫解決方案&#xff0c;充分滿足客戶在不同業務規模下的多…

如何在 Vue 3 中實現百度地圖位置選擇器組件

如何在 Vue 3 中實現百度地圖位置選擇器組件 前言 在開發前端應用時&#xff0c;地圖選擇器是一個非常常見的需求。尤其是在一些需要用戶選擇地址的場景&#xff0c;如電商平臺、旅游網站、酒店預定等&#xff0c;百度地圖組件能提供準確的地理位置服務。在本文中&#xff0c…

Python中如何用正則表達式精準匹配IP地址?

在網絡編程和數據處理時&#xff0c;我們經常需要從文本中提取或驗證IP地址。Python的正則表達式(re模塊)是完成這個任務的利器。但你知道怎么寫才能準確匹配各種合法的IP地址嗎&#xff1f;今天我們就來詳細探討這個問題。 為什么需要IP正則表達式&#xff1f; 假設你正在分…

spring--聲明式事務

聲明式事務 1、回顧事務 要么都成功&#xff0c;要么都失敗&#xff01; 事務在項目開發中&#xff0c;十分重要&#xff0c;涉及數據的一致性問題 確保完整性和一致性 事務ACID&#xff1a; 原子性&#xff1a;事務是原子性操作&#xff0c;由一系列動作組成&#xff0c;…

Kotlin 學習-集合

/*** kotlin 集合* List:是一個有序列表&#xff0c;可通過索引&#xff08;下標&#xff09;訪問元素。元素可以在list中出現多次、元素可重復* Set:是元素唯一的集合。一般來說 set中的元素順序并不重要、無序集合* Map:&#xff08;字典&#xff09;是一組鍵值對。鍵是唯一的…

WPF 五子棋項目文檔

WPF 五子棋項目文檔 1. 項目概述 本項目是一個使用 Windows Presentation Foundation (WPF) 技術棧和 C# 語言實現的桌面版五子棋&#xff08;Gomoku&#xff09;游戲。它遵循 MVVM&#xff08;Model-View-ViewModel&#xff09;設計模式&#xff0c;旨在提供一個結構清晰、可…

計算機操作系統——死鎖(詳細解釋和處理死鎖)

系列文章目錄 計算機操作系統-計算機系統中的死鎖 文章目錄 系列文章目錄前言一、資源問題&#xff1a; 計算機系統當中的死鎖&#xff1a; 二、死鎖的定義、必要條件和處理方法&#xff1a; 1.死鎖的定義&#xff1a;2.產生死鎖的必要條件&#xff1a;3.處理死鎖的方法&#…

Springboot項目正常啟動,訪問資源卻出現404錯誤如何解決?

我在自己的springboot項目中的啟動類上同時使用了SprinBootApplication和ComponentScan注解, 雖然項目能夠正常啟動,但是訪問資源后,返回404錯誤,隨后在啟動類中輸出bean,發現controller創建失敗: 而后我將ComponentScan去掉后資源就能訪問到了. 原因 SprinBootApplication本身…

第十五屆藍橋杯C/C++B組省賽真題講解(分享去年比賽的一些真實感受)

試題A——握手問題 一、解題思路 直接用高中學的排列組合思路 二、代碼示例 #include<bits/stdc.h> using namespace std; int fun(int n) {int sum0;for(int i0;i<n;i){for(int ji1;j<n;j)sum; } return sum; } int main() {cout<<fun(50)-fun(7); }三、…

動態規劃(6)——01背包問題

歡迎來到博主的專欄&#xff1a;算法解析 博主ID&#xff1a;代碼小號 文章目錄 牛客網——【模板】01背包題目解析題目1算法原理題目1題解代碼。問題2算法原理問題2題解代碼01背包問題的滾動數組優化 牛客網——【模板】01背包 題目解析 關于I/O相關的東西博主就不多贅述了&a…

TQTT_KU5P開發板教程---實現流水燈

文檔實現功能介紹 本文檔是學習本開發板的基礎&#xff0c;通過設置計數器使led0到led7依次閃爍&#xff0c;讓用戶初步認識vivado基本的開發流程以及熟悉項目的創建。本開發板的所有教程所使用的軟件都是vivado2024.1版本的。可以根據網上的教程下載與安裝。 硬件資源 此次教程…

Spring 中的 @Cacheable 緩存注解

1 什么是緩存 第一個問題&#xff0c;首先要搞明白什么是緩存&#xff0c;緩存的意義是什么。 對于普通業務&#xff0c;如果要查詢一個數據&#xff0c;一般直接select數據庫進行查找。但是在高流量的情況下&#xff0c;直接查找數據庫就會成為性能的瓶頸。因為數據庫查找的…

SEER: Self-Aligned Evidence Extraction for Retrieval-AugmentedGeneration

一、動機 如何從檢索到的段落中提取證據&#xff0c;以降低計算成本并提升最終的RAG性能&#xff0c;然而這一問題仍然具有挑戰性。 現有方法 嚴重依賴于基于啟發式的增強&#xff0c;面臨以下幾個問題&#xff1a; &#xff08;1&#xff09;由于手工制作的上下文過濾&…

毫米波測試套裝速遞!高效賦能5G/6G、新材料及智能超表面(RIS)研發

德思特&#xff08;Tesight&#xff09;作為全球領先的測試測量解決方案提供商&#xff0c;始終致力于為前沿技術研發提供高精度、高效率的測試工具。 針對毫米波技術在高頻通信、智能超表面&#xff08;RIS&#xff09;、新材料等領域的快速應用需求&#xff0c;我們推出毫米…

三維激光測量助力企業檢測效率提升3倍

智能制造與數字化浪潮席卷下&#xff0c;三維掃描技術已成為工業檢測領域不可或缺的工具。面對傳統檢測手段的精度瓶頸與效率局限&#xff0c;三維掃描儀&#xff0c;以毫米級精度、非接觸式測量與超高速掃描三大核心優勢&#xff0c;為汽車制造、航空航天、消費電子等行業的品…

SQL:Normalization(范式化)

目錄 Normalization&#xff08;范式化&#xff09; 為什么需要 Normalization&#xff1f; &#x1f9e9; 表格分析&#xff1a; 第一范式&#xff08;1NF&#xff09; 什么是第一范式&#xff08;First Normal Form&#xff09;&#xff1f; 第二范式&#xff08;2NF&am…