Linux內核同步機制之completion

? ???內核編程中常見的一種模式是,在當前線程之外初始化某個活動,然后等待該活動的結束。這個活動可能是,創建一個新的內核線程或者新的用戶空間進程、對一個已有進程的某個請求,或者某種類型的硬件動作,等等。在這種情況下,我們可以使用信號量來同步這兩個任務。然而,內核中提供了另外一種機制——completion接口。Completion是一種輕量級的機制,他允許一個線程告訴另一個線程某個工作已經完成。


結構與初始化

?????? Completion在內核中的實現基于等待隊列(關于等待隊列理論知識在前面的文章中有介紹),completion結構很簡單:

[cpp]?view plain?copy
  1. struct?completion?{??
  2. ????unsigned?int?done;/*用于同步的原子量*/??
  3. ????wait_queue_head_t?wait;/*等待事件隊列*/??
  4. };??

和信號量一樣,初始化分為靜態初始化和動態初始化兩種情況:

靜態初始化:

[cpp]?view plain?copy
  1. #define?COMPLETION_INITIALIZER(work)?\??
  2. ????{?0,?__WAIT_QUEUE_HEAD_INITIALIZER((work).wait)?}??
  3. ??
  4. #define?DECLARE_COMPLETION(work)?\??
  5. ????struct?completion?work?=?COMPLETION_INITIALIZER(work)??

動態初始化:

[cpp]?view plain?copy
  1. static?inline?void?init_completion(struct?completion?*x)??
  2. {??
  3. ????x->done?=?0;??
  4. ????init_waitqueue_head(&x->wait);??
  5. }??

? ? ?可見,兩種初始化都將用于同步的done原子量置位了0,后面我們會看到,該變量在wait相關函數中減一,在complete系列函數中加一。

?

實現

? ? ? ?同步函數一般都成對出現,completion也不例外,我們看看最基本的兩個complete和wait_for_completion函數的實現。

wait_for_completion最終由下面函數實現:

[cpp]?view plain?copy
  1. static?inline?long?__sched??
  2. do_wait_for_common(struct?completion?*x,?long?timeout,?int?state)??
  3. {??
  4. ????if?(!x->done)?{??
  5. ????????DECLARE_WAITQUEUE(wait,?current);??
  6. ??
  7. ????????wait.flags?|=?WQ_FLAG_EXCLUSIVE;??
  8. ????????__add_wait_queue_tail(&x->wait,?&wait);??
  9. ????????do?{??
  10. ????????????if?(signal_pending_state(state,?current))?{??
  11. ????????????????timeout?=?-ERESTARTSYS;??
  12. ????????????????break;??
  13. ????????????}??
  14. ????????????__set_current_state(state);??
  15. ????????????spin_unlock_irq(&x->wait.lock);??
  16. ????????????timeout?=?schedule_timeout(timeout);??
  17. ????????????spin_lock_irq(&x->wait.lock);??
  18. ????????}?while?(!x->done?&&?timeout);??
  19. ????????__remove_wait_queue(&x->wait,?&wait);??
  20. ????????if?(!x->done)??
  21. ????????????return?timeout;??
  22. ????}??
  23. ????x->done--;??
  24. ????return?timeout??:?1;??
  25. }??

而complete實現如下:

[cpp]?view plain?copy
  1. void?complete(struct?completion?*x)??
  2. {??
  3. ????unsigned?long?flags;??
  4. ??
  5. ????spin_lock_irqsave(&x->wait.lock,?flags);??
  6. ????x->done++;??
  7. ????__wake_up_common(&x->wait,?TASK_NORMAL,?1,?0,?NULL);??
  8. ????spin_unlock_irqrestore(&x->wait.lock,?flags);??
  9. }??

? ? ? ?不看內核實現的源代碼我們也能想到他的實現,不外乎在wait函數中循環等待done變為可用(正),而另一邊的complete函數為喚醒函數,當然是將done加一,喚醒待處理的函數。是的,從上面的代碼看到,和我們想的一樣。內核也是這樣做的。


運用

運用LDD3中的例子:

[cpp]?view plain?copy
  1. #include?<linux/module.h>??
  2. #include?<linux/init.h>??
  3. ??
  4. #include?<linux/sched.h>??
  5. #include?<linux/kernel.h>??
  6. #include?<linux/fs.h>??
  7. #include?<linux/types.h>??
  8. #include?<linux/completion.h>??
  9. ??
  10. MODULE_LICENSE("GPL");??
  11. ??
  12. static?int?complete_major=250;??
  13. DECLARE_COMPLETION(comp);??
  14. ??
  15. ssize_t?complete_read(struct?file?*filp,char?__user?*buf,size_t?count,loff_t?*pos)??
  16. {??
  17. ????printk(KERN_ERR?"process?%i?(%s)?going?to?sleep\n",current->pid,current->comm);??
  18. ????wait_for_completion(&comp);??
  19. ????printk(KERN_ERR?"awoken?%i?(%s)\n",current->pid,current->comm);??
  20. ????return?0;??
  21. }??
  22. ??
  23. ssize_t?complete_write(struct?file?*filp,const?char?__user?*buf,size_t?count,loff_t?*pos)??
  24. {??
  25. ????printk(KERN_ERR?"process?%i?(%s)?awakening?the?readers...\n",current->pid,current->comm);??
  26. ????complete(&comp);??
  27. ????return?count;??
  28. }??
  29. ??
  30. struct?file_operations?complete_fops={??
  31. ????.owner=THIS_MODULE,??
  32. ????.read=complete_read,??
  33. ????.write=complete_write,??
  34. };??
  35. ??
  36. int?complete_init(void)??
  37. {??
  38. ????int?result;??
  39. ????result=register_chrdev(complete_major,"complete",&complete_fops);??
  40. ????if(result<0)??
  41. ????????return?result;??
  42. ????if(complete_major==0)??
  43. ????????complete_major=result;??
  44. ????return?0;??
  45. }??
  46. void?complete_cleanup(void)??
  47. {??
  48. ????unregister_chrdev(complete_major,"complete");??
  49. }??
  50. module_init(complete_init);??
  51. module_exit(complete_cleanup);??

測試步驟:

1,?mknod /dev/complete創建complete節點,在linux上驅動程序需要手動創建文件節點。

2,?insmod complete.ko 插入驅動模塊,這里要注意的是,因為我們的代碼中是手動分配的設備號,很可能被系統已經使用了,所以如果出現這種情況,查看/proc/devices文件。找一個沒有被使用的設備號。

3,?cat /dev/complete 用于讀該設備,調用設備的讀函數

4,?打開另一個終端輸入 echo “hello” > /dev/complete 該命令用于寫入該設備。

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

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

相關文章

可變參數函數(一)

一個函數可以接受不定數的參數個數&#xff0c;這就是可變參數函數&#xff0c;比較常見的比如printf(),scanf()&#xff1b; printf(const char* format,…); printf(“%d”,i); printf(“%s”,s); printf(“the number is %d,stirng is :%s”,i,s); 變量參數函數的簡單實現&a…

Linux內核線程kernel thread詳解--Linux進程的管理與調度

內核線程為什么需要內核線程Linux內核可以看作一個服務進程(管理軟硬件資源&#xff0c;響應用戶進程的種種合理以及不合理的請求)。 內核需要多個執行流并行&#xff0c;為了防止可能的阻塞&#xff0c;支持多線程是必要的。 內核線程就是內核的分身&#xff0c;一個分身可以處…

可變參數函數(二)

函數樣例&#xff1a; #include<stdio.h> #include<stdlib.h> #include<stdarg.h>double add(int n,...) {int i 0;double sum 0;va_list argptr;va_start(argptr,n);for(i 0 ; i < n; i){double d va_arg(argptr,double);printf("%d argument …

Linux 內核網絡協議棧 ------sk_buff 結構體 以及 完全解釋 (2.6.16)

在2.6.24之后這個結構體有了較大的變化&#xff0c;此處先說一說2.6.16版本的sk_buff&#xff0c;以及解釋一些問題。一、先直觀的看一下這個結構體~~~~~~~~~~~~~~~~~~~~~~在下面解釋每個字段的意義~~~~~~~~~~~[cpp] view plaincopyprint?struct sk_buff { /* These…

可變參數輸出(三)

Linux C關于輸出函數的定義&#xff1a; int printf(const char *format,…); int vprintf(const char * format,va_list ap); int vfprintf(FILE *stream,cosnt char *format,va_list ap); int vsprintf(char *str,const char *format,va_list ap); int vsnprintf(char *str,s…

最常用的設計模式---適配器模式(C++實現)

適配器模式屬于結構型的設計模式&#xff0c;它是結構型設計模式之首&#xff08;用的最多的結構型設計模式&#xff09;。 適配器設計模式也并不復雜&#xff0c;適配器它是主要作用是將一個類的接口轉換成客戶希望的另外一個接口這樣使得原本由于接口不兼容而不能一起工作的那…

Linux 簡單打印日志(二)

#include<stdio.h> #include<stdlib.h> #include<string.h> #include<time.h> //#include<windows.h> #include <unistd.h> // linux下頭文件#define FILE_MAX_SIZE (1024*1024)void get_local_time(char* buffer){time_t rawtime;struct …

程序隨筆——C++實現的一個線程池

1.線程池簡介 我們知道在線程池是一種多線程處理形式&#xff0c;處理過程中我們將相應的任務提交給線程池&#xff0c;線程池會分配對應的工作線程執行任務或存放在任務隊列中&#xff0c;等待執行。 面向對象編程中&#xff0c;創建和銷毀對象是需要消耗一定時間的&#xff0…

線程池原理及創建并C++實現

本文給出了一個通用的線程池框架&#xff0c;該框架將與線程執行相關的任務進行了高層次的抽象&#xff0c;使之與具體的執行任務無關。另外該線程池具有動態伸縮性&#xff0c;它能根據執行任務的輕重自動調整線程池中線程的數量。文章的最后&#xff0c;我們給出一個簡單示例…

Linux 打印簡單日志(一)

簡單日志輸出&#xff1a; #include<stdio.h> #include<string.h> #include<stdlib.h>void write(char* filename,char* szStr){FILE* fp;fp fopen(filename,"at");if(fp ! NULL){fwrite(szStr,256,1,fp); //fclose(fp);fp NULL;} }int main(int…

c++簡單線程池實現

線程池&#xff0c;簡單來說就是有一堆已經創建好的線程&#xff08;最大數目一定&#xff09;&#xff0c;初始時他們都處于空閑狀態&#xff0c;當有新的任務進來&#xff0c;從線程池中取出一個空閑的線程處理任務&#xff0c;然后當任務處理完成之后&#xff0c;該線程被重…

Linux 打印可變參數日志

實現了傳輸進去的字符串所在的文檔&#xff0c;函數和行數顯示功能。 實現了將傳入的可變參數打印到日志功能。 #include<stdio.h> #include<stdarg.h> #include<string.h>const char * g_path "/home/exbot/wangqinghe/log.txt"; #define LOG(fm…

C++強化之路之線程池開發整體框架(二)

一.線程池開發框架 我所開發的線程池由以下幾部分組成&#xff1a; 1.工作中的線程。也就是線程池中的線程&#xff0c;主要是執行分發來的task。 2.管理線程池的監督線程。這個線程的創建獨立于線程池的創建&#xff0c;按照既定的管理方法進行管理線程池中的所有線程&#xf…

vfprintf()函數

函數聲明&#xff1a;int vfprintf(FILE *stream, const char *format, va_list arg) 函數參數&#xff1a; stream—這是指向了FILE對象的指針&#xff0c;該FILE對象標識了流。 format—c語言字符串&#xff0c;包含了要被寫入到流stream中的文本。它可以包含嵌入的format標簽…

Makefile(二)

將生產的.o文件放進指定的文件中&#xff08;先創建該文件夾&#xff09; src $(wildcard ./*.cpp) obj $(patsubst %.cpp,./output/%.o,$(src))target test$(target) : $(obj)g $(obj) -o $(target) %.o: %.cppg -c $< -o output/$.PHONY:clean clean:rm -f $(target) $…

TCP粘包問題分析和解決(全)

TCP通信粘包問題分析和解決&#xff08;全&#xff09;在socket網絡程序中&#xff0c;TCP和UDP分別是面向連接和非面向連接的。因此TCP的socket編程&#xff0c;收發兩端&#xff08;客戶端和服務器端&#xff09;都要有成對的socket&#xff0c;因此&#xff0c;發送端為了將…

UML類圖符號 各種關系說明以及舉例

UML中描述對象和類之間相互關系的方式包括&#xff1a;依賴&#xff0c;關聯&#xff0c;聚合&#xff0c;組合&#xff0c;泛化&#xff0c;實現等。表示關系的強弱&#xff1a;組合>聚合>關聯>依賴 相互間關系 聚合是表明對象之間的整體與部分關系的關聯&#xff0c…

尋找數組中第二大數

設置兩個數值來表示最大數和第二大數&#xff0c;在循環比較賦值即可 //找給定數組中第二大的數int get_smax(int *arr,int length) {int max;int smax;if(arr[0] > arr[1]){max arr[0];smax arr[1];}else{max arr[1];smax arr[0];}for(int i 2; i < length; i){if(…

timerfd API使用總結

timerfd 介紹 timerfd 是在Linux內核2.6.25版本中添加的接口&#xff0c;其是Linux為用戶提供的一個定時器接口。這個接口基于文件描述符&#xff0c;所以可以被用于select/poll/epoll的場景。當使用timerfd API創建多個定時器任務并置于poll中進行事件監聽&#xff0c;當沒有可…

#if/#else/#endif

在linux環境下寫c代碼時會嘗試各種方法或調整路徑&#xff0c;需要用到#if #include<stdio.h>int main(){int i; #if 0i 1; #elsei 2; #endifprintf("i %d",i);return 0; } 有時候會調整代碼&#xff0c;但是又不是最終版本的更換某些值&#xff0c;就需要注…