文章目錄
- 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、前言
- 學習參考書籍以及本文涉及的示例程序:李山文的《Linux驅動開發進階》
- 本文屬于個人學習后的總結,不太具備教學功能。
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);