【代码拾遗】linux内核中延时机制之msleep?如何利用msleep实现间歇性确认硬件状态的函数?(不可中断方式睡眠)

背景

msleep 函数的核心逻辑是将毫秒数转换为内核的 jiffies 单位,并通过 schedule_timeout_uninterruptible 让当前进程进入不可中断的睡眠状态。

细节

/**
 * msleep - sleep safely even with waitqueue interruptions
 * @msecs: Time in milliseconds to sleep for
 */
void msleep(unsigned int msecs)
{
	unsigned long timeout = msecs_to_jiffies(msecs) + 1;

	while (timeout)
		timeout = schedule_timeout_uninterruptible(timeout);
}

可以看到将等待的毫秒换算为jiffies 然后 + 1,这里+1 是一个工程约等于。为了确保睡眠时间不会少于指定的毫秒数。msecs_to_jiffies 函数将毫秒转换为 jiffies,但由于 jiffies 是一个离散的计时单位,可能会导致精度损失。也体现了msleep精度并不是准确的毫秒。
另外可以看到msleep使用了while循环,但是会让出调度器。所以msleep会阻塞在这里。然后调度器每次调度回来msleep所在的执行单元后,会判断timeout是否已经timeout。进程会持续以不可中断的方式睡眠,直到timeout超时或者被其他机制唤醒。并且每次唤醒后会更新timeout的值。
其中schedule_timeout_uninterruptible在给定的超时时间内让当前进程进入睡眠状态,并且在睡眠期间不会被信号中断。睡眠期间,进程不会响应信号,但可以被内核的定时器机制或其他内核机制唤醒。如果超时时间到达,进程会被内核调度器唤醒。

这里所谓的不可中断为什么又能被其他机制唤醒呢?

这里涉及到Linux 内核的调度机制和信号处理机制。
“不可中断”(TASK_UNINTERRUPTIBLE)状态主体是进程。调用msleep的进程不会被信号(如 SIGINT(比如ctrl + c中断)、SIGTERM 等)唤醒。当进程处于这种状态时,它不会响应用户空间的信号中断,必须等到它自己完成睡眠或者被其他机制唤醒。
这里的中断不是只芯片的中断,不是一个概念和层次,他是一个进程能否被“叫停”。

内核驱动例子

static int wait_fw_init(struct mlx5_core_dev *dev, u32 max_wait_mili,
			u32 warn_time_mili)
{
	unsigned long warn = jiffies + msecs_to_jiffies(warn_time_mili);
	unsigned long end = jiffies + msecs_to_jiffies(max_wait_mili);
	u32 fw_initializing;
	int err = 0;

	do {
		fw_initializing = ioread32be(&dev->iseg->initializing);
		if (!(fw_initializing >> 31))
			break;
		if (time_after(jiffies, end) ||
		    test_and_clear_bit(MLX5_BREAK_FW_WAIT, &dev->intf_state)) {
			err = -EBUSY;
			break;
		}
		if (warn_time_mili && time_after(jiffies, warn)) {
			mlx5_core_warn(dev, "Waiting for FW initialization, timeout abort in %ds (0x%x)\n",
				       jiffies_to_msecs(end - warn) / 1000, fw_initializing);
			warn = jiffies + msecs_to_jiffies(warn_time_mili);
		}
		msleep(mlx5_tout_ms(dev, FW_PRE_INIT_WAIT));
	} while (true);

	return err;
}

这里是网卡的驱动程序定义了一个等待行为,以阻塞方式,间歇性的读取硬件状态是否就绪,但是在等待期间会让出调度器,并且不希望被信号中断。

代码模板

基于前面提到的例子,我们可以实现一个基于msleep的间歇性check函数模板,类似如下(并未调试)
下面例子实现一个等待函数,指定一个最长等待时间T1后,就会退出,但是等待期间会被分成多个时间窗口,每个时间窗口检测一次,每个时间窗口间隔T2;并且会在每间隔T3时间打印一次告警。
比如最长等待120s,每间隔1ms检测一次,每间隔1s打印一次。那就会在每次检测的期间,判断是否和上次间隔1s,如果间隔1s打印告警。如果和最开始间隔超过最长时间直接退出。
这里通过总分的方式再加状态check方式来查看硬件状态,既避免不停查询,也避免等待过久,并且对外还提供了一个总体时间。是一种不错的对外交付接口行为的表现。

#define MY_DRIVER_OK 0
int get_driver_status()
{
	//读取硬件状态比如ioremap的地址用ioread或者其他
}
static int my_wait_pending(u32 max_wait_msec, u32 check_interfal, u32 warning_inter_msec)
{
	//# max_wait_msec T1
	//# check_interfal T2
	//# warning_inter_msec T3
	unsigned long warn = jiffies + msecs_to_jiffies(warning_inter_msec);
	unsigned long end = jiffies + msecs_to_jiffies(max_wait_msec);
	u32 flag = 0;
	int err = 0;
	do {
		//check status for driver
		// if check ok  break
		flag = get_driver_status(); //醒来后马上确认状态
		if (flag == MY_DRIVER_OK) {
			break; 
		}

		if (time_after(jiffies, end)) { //已经超出最长时间了
			printk(KERN_ERR "timeout...");
			err = -EBUSY;
			break; 
		}

		if (warning_inter_msec && time_after(jiffies, warn)) { //固定时间对外提示告警
			printk(KERN_WARN "timeout abort in %d seconds. Current turn check status is 0x%x\n", jiffies_to_msecs(end - warn) / 1000, flag); //打印还将等待多长时间后就会退出
			warn = jiffies + msecs_to_jiffies(warn_time_msec);
		}

		//进入睡眠让出cpu
		msleep(check_interfal); //固定check_interfal时间 醒来进行确认
	} while (true);

	return error; //最后返回异常状态
}

调用方式:
my_wait_pending(120 * 1000, 1, 1 * 1000); //入参单位是毫秒,分别最长等待120s,每间隔1ms检测一次,每间隔1s打印一次

所以该模板实现了最长等待120s,每间隔1ms检测一次,每间隔1s打印一次。那就会在每次检测的期间,判断是否和上次间隔1s,如果间隔1s打印告警。如果和最开始间隔超过最长时间直接退出。
当然还有一种实现方式比较原始的行为大体伪代码类似:

#define MAX_WAIT_TIME (120 * 1000) //ms
#define CHECK_INTERNAL 1 //ms
#define WARNING_INTERNAL (1 * 1000) //ms

for (int window = 0, warning_cnt = 0; window < (MAX_WAIT_TIME / CHECK_INTERNAL); windows ++)\
{
	//check
	if checkflag ok : break;

	//max timeout 这里不用判断,for循环会限制

	//warning打印
	warning_cnt ++;
	if warning_cnt == WARNING_INTERNAL / CHECK_INTERNAL: 
		warning_cnt = 0;
		printk waning
	msleep(CHECK_INTERNAL);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值