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.mzph.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;就需要注…