IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux 里进程阻塞是如何实现的 -> 正文阅读

[系统运维]Linux 里进程阻塞是如何实现的


前言

以下文章要解决的问题:

  1. Linux编程中的阻塞是怎么回事?
  2. Linux编程中的阻塞是怎么实现的?
  3. 进程的阻塞和线程的阻塞的区别?
  4. 阻塞之后是如何被唤醒的?
  5. 内核是如何识别被阻塞的线程的,保存了什么?

一、Linux编程中的阻塞是怎么回事

阻塞就是将应用程序相对应的进程挂起。

二、误区一: 进程阻塞消耗CPU资源

以前认为进程阻塞是不好的,总会将默认阻塞的I/O设置为非阻塞的I/O,这样程序不会一直等待事件的到来。
其实对于CPU来说,阻塞当前进程,就会有时间运行其他进程,不浪费资源。
那么,进程被阻塞之后,CPU会立马执行别的进程吗?CPU还会回来尝试运行被阻塞的进程吗?这些问题都会影响系统的效率

三、CPU 的时间片轮转机制

单CPU为了实现程序的并发执行,采取了最简单,最古老,使用范围最广的时间片轮转机制。也就是在时间片使用完的时候,发现还有进程在执行,则会剥夺当前进程的CPU使用权分配给其他的进程,但是在时间片使用完之前,当前进程被阻塞或者结束,CPU当即进行切换。
但是,由于时间片的间隔时间为5ms或者100ms,人是无法感觉到进程切换的卡顿的。
所以我们就知道了,进程被阻塞之后,会立即运行其他的进程

四、工作队列和等待队列

  • 接下来分析,CPU还会回来尝试运行被阻塞的进程吗?
    如图内核中维护一个工作队列,因为时间片轮询的作用,会在进程A,B,C等多个进程之间轮询的跑,这就是进程调度了
    在这里插入图片描述
    现在假设进程A中某个对象调用了某个方法,引起了进程A的阻塞,内核立刻将进程A从工作队列中移除,同时在该对象中创建等待队列,并加入到等待队列项中。
    在这里插入图片描述
    由上可知,进程A被提出在调度之外,不再受到调度子系统的控制,这就是我们常说的,进程被挂起。这里体现了进程挂起和阻塞的关系。阻塞是人为的动作,主动调用阻塞方法,让程序停下来某一位置。而挂起是阻塞的实现方式。
  • 如何唤醒被阻塞的程序?
    当这个对象受到某种“刺激”(某事件触发)之后, 操作系统将该对象等待队列上的进程重新放回到工作队列上就绪,等待时间片轮转到该进程
    受到的是什么刺激?如何受到刺激?(将来补充)

4.1 工作队列

4.1.1 什么是工作队列?

工作队列是区别于软中断和tasklet微线程的一种将任务推后执行的方式,工作队列将任务推后执行其实是将任务交给了内核线程去执行。正因为是线程执行,所以任务是执行在进程的上下文中,重要的是工作队列可以被重新调度甚至睡眠。

4.1.2 同是底半部的实现,与softirq&tasklet微线程的比较

相同点:都是可以底半部的方式。与tasklet一样,工作队列只能运行在一个cpu上。
不同点:
触发时机不同:软中断和tasktlet归根结底是运行在中断的上下文中。工作队列是可以被调度的运行在进程的上下文中。
运行条件不同:软中断和takslet运行在本地中断打开的情况下运行的,所以仍然不能有运行可能会导致程序休眠或者延时的动作。

4.1.3 什么情况下使用tasklet&软中断,以及工作队列呢?

当考虑在中断中获取大量内存,信号量,以及使用阻塞式IO,睡眠,需要一个调度实体的情况下,考虑使用内核线程来推迟执行动作时用工作队列,否则考虑使用tasklet。

4.1.4 内核线程和工作队列的关系?

对于每一个工作队列来说,内核总会创建一个守护线程来处理工作队列。也就是说每一个工作队列都有一个与其相对的内核线程。工作时,内核线程轮询的处理这个工作队列上的所有工作节点对应的处理函数(和tasklet的串行执行相似),工作队列由一个workqueue_struct结构体来描述。

4.1.5 如何使用工作队列?

首先明确两个概念:(1)使用内核默认提供的工作队列(2)自己创建工作队列

/*
 * The externally visible workqueue abstraction is an array of
 * per-CPU workqueues:
 */
struct workqueue_struct {
	unsigned int		flags;		/* I: WQ_* flags */
 
//这个共用体表示该workqueue_struct属于哪个CPU的队列。
	union {
		struct cpu_workqueue_struct __percpu	*pcpu;
		struct cpu_workqueue_struct		*single;
		unsigned long				v;
	} cpu_wq;				/* I: cwq's */
	struct list_head	list;		/* W: list of all workqueues */
//用来连接work_struct的队列头
	struct mutex		flush_mutex;	/* protects wq flushing */
	int			work_color;	/* F: current work color */
	int			flush_color;	/* F: current flush color */
	atomic_t		nr_cwqs_to_flush; /* flush in progress */
	struct wq_flusher	*first_flusher;	/* F: first flusher */
	struct list_head	flusher_queue;	/* F: flush waiters */
	struct list_head	flusher_overflow; /* F: flush overflow list */
 
	mayday_mask_t		mayday_mask;	/* cpus requesting rescue */
	struct worker		*rescuer;	/* I: rescue worker */
 
	int			saved_max_active; /* W: saved cwq max_active */
	const char		*name;		/* I: workqueue name */
#ifdef CONFIG_LOCKDEP
	struct lockdep_map	lockdep_map;
#endif
};


workqueue_struct结构体比较复杂,一般没有必要了解所有成员的含义。在workqueue_struct涉及到一个cpu_workqueue_struct结构体,该结构体有什么用呢?
与原来的tasklet一样,一个工作队列也是只能工作在一个CPU上面的,即每一个CPU都有一个工作队列。而cpu_workqueue_sruct就是描述该CPU的工作队列的结构体

/*
 * The per-CPU workqueue.  The lower WORK_STRUCT_FLAG_BITS of
 * work_struct->data are used for flags and thus cwqs need to be
 * aligned at two's power of the number of flag bits.
 */
struct cpu_workqueue_struct {
	struct global_cwq	*gcwq;		/* I: the associated gcwq */
	struct workqueue_struct *wq;		/* I: the owning workqueue ,指向属于该CPU的workqueue_struct结构体*/
	int			work_color;	/* L: current color */
	int			flush_color;	/* L: flushing color */
	int			nr_in_flight[WORK_NR_COLORS];
						/* L: nr of in_flight works */
	int			nr_active;	/* L: nr of active works */
	int			max_active;	/* L: max active works */
	struct list_head	delayed_works;	/* L: delayed works */
};


了解了上面两个结构体之后,我们因该能够大致了解工作队列的工作机制,大体上与tasklet差不多。下面我们就来看一下工作队列最为重要的成员----工作,work_struct。work_struct是工作队列里面的成员,里面会定义该work_struct的处理函数。

struct work_struct {
	atomic_long_t data;
	struct list_head entry;  //指向与其相邻的前后两个work_struct
	work_func_t func;  //该work_struct节点的处理函数。
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};


这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。

  • 使用内核共享工作队列
    在linux中,为了方便用户编程,内核提供了一个默认的公共的工作队列(kevent),当内核启动时就创建了一个kevent的工作队列,处于睡眠状态,只有在驱动中调用,schedule_work,此工作队列才被唤醒,执行work_struct中的动作,完毕进入休眠状态。
    因此采用共享工作队列,在用户的实现上是非常简单的。
    第一步:编写自己的 work_struct 工作函数。
    第二步:定义自己的 work_struct 结构体。
    第三步:初始化work_struct结构体,使工作函数地址指向work_struct ->func
    第四步:可以在适当位置使用schedule_work函数完成向系统工作队列添加自己的work_struct

  • 采用自定义动作队列
    采用共享工作队列会有一个弊端,因为毕竟共享队列采用的是kevent线程,系统里面的其它工作也会使用到该共享队列。如果我们在该工作队列加入太多耗时的程序,无疑会降低系统性能,因此一般在驱动程序中,我们会偏向于使用自定义工作队列,采用自定义工作队列也比较简单,相对于共享工作队列,这里多了一个创建自定义工作的函数,即:create_queue函数(注意这个函数会在每一个CPU上都创建一个一个工作队列和相应的线程,这未免太过于消耗资源,因此我们还可以采用在某一指定的CPU上创建一个工作队列,例如采用create_singlethread_workqueue函数,就会在编号为第一个的CPU上创建内核线程和工作队列。)对于自定义的工作队列,在这里我们不能使用schedule_struct函数将work_struct添加进工作队列了,这是因为schedule_work函数只能往共享工作队列上添加工作节点(work_struct),所以我们必须要采用queue_work 函数。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/timer.h>  /*timer*/
#include <asm/uaccess.h>  /*jiffies*/
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
struct tasklet_struct task_t ; 
struct workqueue_struct *mywork ;
//定义一个工作队列结构体
struct work_struct work;
static void task_fuc(unsigned long data)
{
	if(in_interrupt()){
             printk("%s in interrupt handle!\n",__FUNCTION__);
        }
}
//工作队列处理函数
static void mywork_fuc(struct work_struct *work)
{
	if(in_interrupt()){
             printk("%s in interrupt handle!\n",__FUNCTION__);
        }
	msleep(2);
	printk("%s in process handle!\n",__FUNCTION__);
}
 
static irqreturn_t irq_fuction(int irq, void *dev_id)
{	
	tasklet_schedule(&task_t);
	//调度工作
	schedule_work(&work);
	if(in_interrupt()){
	     printk("%s in interrupt handle!\n",__FUNCTION__);
	}
	printk("key_irq:%d\n",irq);
	return IRQ_HANDLED ;
}
	
static int __init tiny4412_Key_irq_test_init(void) 
{
	int err = 0 ;
	int irq_num1 ;
	int data_t = 100 ;
	//创建新队列和新工作者线程
	mywork = create_workqueue("my work");
	//初始化
	INIT_WORK(&work,mywork_fuc);
	//调度指定队列
	queue_work(mywork,&work);
	tasklet_init(&task_t,task_fuc,data_t);
	printk("irq_key init\n");
	irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));
	err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,"tiny4412_key1",(void *)"key1");
	if(err != 0){
		free_irq(irq_num1,(void *)"key1");
		return -1 ;
	}
	return 0 ;
}
 
static void __exit tiny4412_Key_irq_test_exit(void) 
{
	int irq_num1 ;
	printk("irq_key exit\n");
	irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));
	//销毁一条工作队列
	destroy_workqueue(mywork);
	free_irq(irq_num1,(void *)"key1");
}
 
module_init(tiny4412_Key_irq_test_init);
module_exit(tiny4412_Key_irq_test_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYX");
MODULE_DESCRIPTION("Exynos4 KEY Driver");


4.1.6 工作队列的组织架构

组织结构即:workqueue_struct,cpu_workqueue_struct,与work_struct之间的关系。每一个工作队列对应一个workqueue_struct,cpu_workqueue_struct描述了此工作队列对应的cpu,work_struct 对应其上的具体工作任务。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
相关API

创建一个队列就会有一个内核线程,一般不要轻易创建队列
位于进程上下文--->可以睡眠
定义:
	struct work_struct work;
 
初始化:
	INIT_WORK(struct work_struct *work, void (*func)(struct work_struct *work));
 
定义并初始化:
	DECLARE_WORK(name, void (*func)(struct work_struct *work));
 
===========================================================
共享队列: 
调度:
	int schedule_work(struct work_struct *work);
	返回1成功, 0已经添加在队列上
 
延迟调度:
	int schedule_delayed_work(struct work_struct *work, unsigned long delay);
 
===========================================================
自定义队列: 
创建新队列和新工作者线程:
	struct workqueue_struct *create_workqueue(const char *name);
 
调度指定队列:
	int queue_work(struct workqueue_struct *wq, struct work_struct *work);
 
延迟调度指定队列:
	int queue_delayed_work(struct workqueue_struct *wq, 
			struct work_struct *work, unsigned long delay);
销毁队列:
	void destroy_workqueue(struct workqueue_struct *wq);


4.2 等待队列

  • 什么是等待队列?
  • 如何使用等待队列?
  • 如何加入到等待队列
  • 等待队列是如何被唤醒的?

五、误区二:阻塞进程不消耗系统资源

系统资源不单指的是CPU,还包括内存,io,磁盘,进程只是被阻塞而不是被消灭。仍然会占用内存资源。

六、调度会用到中断吗?pendSV的特性?


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-05-10 12:16:32  更:2022-05-10 12:17:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 15:39:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码