Notes15进程休眠,时间和延时,延缓

Posted 码农编程录

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Notes15进程休眠,时间和延时,延缓相关的知识,希望对你有一定的参考价值。


1.实现进程休眠:条件不够歇一歇,把CPU让给其他进程

有时候进程在读设备时,发现设备数据还没准备好,没办法正常读取设备。或在写设备时,发现设备缓冲区为满,没办法正常写设备。在遇到这些情况时,进程该何去何从?进程在操作设备时,如果条件不满足,就让它进入休眠等待,直到条件满足,就可唤醒进程进行后面操作,

/*
初始化:
	DECLARE_WAIT_QUEUE_HEAD()   //初始化等待队列头,宏的静态方式
	或
	wait_queue_head_t wq;     //动态方式
	init_waitqueue_head(&wq);
休眠:	
	wait_event()   //不可被打断,死等,直到满足条件为止
	wait_event_interruptible()  //可使用信号打断它,常用
唤醒:	
	wake_up()   //对应wait_event()
	wake_up_interruptible()	 //对应上面可中断方式休眠的进程
*/
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/slab.h>
#include<linux/uaccess.h>
#include<linux/sched.h>
#include<linux/semaphore.h>
#include<linux/mutex.h>
#include <linux/wait.h>
#define LOCK_USE 1 //0:semaphore,1:mutex
#define HELLO_MAJOR 0
#define HELLO_NR_DEVS 2

int hello_major = HELLO_MAJOR;
int hello_minor = 0;
dev_t devt;      //高12位是主设备号,低20位是次设备号
int hello_nr_devs = HELLO_NR_DEVS;

module_param(hello_major, int, S_IRUGO);
module_param(hello_minor, int, S_IRUGO);
module_param(hello_nr_devs, int, S_IRUGO);

//111111111111111111111111111111111111111111111111111111111111111111111
struct hello_char_dev		//实际的字符设备结构,类似于面向对象的继承
	struct cdev cdev;
	char *c;
	int n;
	struct semaphore sema;
	struct mutex mtx;
;
struct hello_char_dev *hc_devp;
struct class *hc_cls;
DECLARE_WAIT_QUEUE_HEAD(wq);  //等待队列头

//1111111111111111111111111111111111111111111111111111111111111111111
int hc_open(struct inode *inode, struct file *filp)

	struct hello_char_dev *hc_dev;
	printk(KERN_INFO "%s open \\n",current->comm);
	hc_dev = container_of(inode->i_cdev,struct hello_char_dev,cdev);  //获取设备结构体的地址
	filp->private_data = hc_dev;		//将设备结构地址放到文件描述符结构的私有数据中

	return 0;


//1111111111111111111111111111111111111111111111111111111111111111111
ssize_t hc_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)

	ssize_t retval=0;
	struct hello_char_dev *hc_dev=filp->private_data;
	printk(KERN_INFO "read hc_dev %p\\n",hc_dev);
 // wait_event(wq,hc_dev->c!=NULL);  //第一个参数:等待队列头。第二个参数:等待条件: 如果为空就进入等待,不为空执行后面操作。
	wait_event_interruptible(wq,hc_dev->c!=NULL);
	if(*f_pos >= hc_dev->n)
		goto out;
	if(*f_pos + count > hc_dev->n)
		count = hc_dev->n - *f_pos;
	
	if(copy_to_user(buf,hc_dev->c,count))
	
		retval = -EFAULT;
		goto out;
	

	*f_pos += count;
	return count;	
out:
	return retval;


//1111111111111111111111111111111111111111111111111111111111111111111111
ssize_t hc_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)

	struct hello_char_dev *hc_dev=filp->private_data;
	int retval = -ENOMEM;
	printk(KERN_INFO "%s write begin\\n",current->comm);
	#if (LOCK_USE==0)
	if(down_interruptible(&hc_dev->sema))		//-EINTR
		return -ERESTARTSYS;
	#endif
	#if (LOCK_USE==1)
	if(mutex_lock_interruptible(&hc_dev->mtx))   //-EINTR
		return -ERESTARTSYS;
	#endif

	kfree(hc_dev->c);
	hc_dev->c=NULL;
	hc_dev->n=0;
	hc_dev->c = kzalloc(count,GFP_KERNEL);
	if(!hc_dev->c)
		goto out;
	if(copy_from_user(hc_dev->c,buf,count))
	
		retval = -EFAULT;
		goto fail_copy;
	
	hc_dev->n = count;
	
	#if (LOCK_USE==0)
	up(&hc_dev->sema);
	#endif
	#if (LOCK_USE==1)
	mutex_unlock(&hc_dev->mtx);
	#endif
	printk(KERN_INFO"%s write done",current->comm);
	
//	wake_up(&wq);  // 当写入字符设备成功后(如上)就调用wakeup函数唤醒等待队列上的进程。
	wake_up_interruptible(&wq);
	return count;	 
fail_copy:
	kfree(hc_dev->c);
out:
	#if (LOCK_USE==0)
	up(&hc_dev->sema);
	#endif
	#if (LOCK_USE==1)
	mutex_unlock(&hc_dev->mtx);
	#endif
	return retval;					//不能返回0,否则会不停的写


//1111111111111111111111111111111111111111111111111111111111111111111
int hc_release(struct inode *inode, struct file *filp)

	printk(KERN_INFO "%s release\\n",current->comm);
	return 0;


//11111111111111111111111111111111111111111111111111111111111111111111
struct file_operations hc_fops = 		//字符设备的操作函数
	.owner =    THIS_MODULE,
	.read =     hc_read,
	.write =    hc_write,
	.open =     hc_open,
	.release =  hc_release,
;

//1111111111111111111111111111111111111111111111111111111111111111111
static int __init hello_init(void)	

	int ret,i;
	printk(KERN_INFO "---BEGIN HELLO LINUX MODULE---\\n");
	if(hello_major)
		devt=MKDEV(hello_major,hello_minor);
		ret=register_chrdev_region(devt,hello_nr_devs,"hello_chr");	//使用指定的设备号分配
	
	else
		ret = alloc_chrdev_region(&devt,hello_minor,hello_nr_devs,"hello_chr");//动态分配主设备号
		hello_major = MAJOR(devt);
	
	if (ret < 0) 
		printk(KERN_WARNING "hello: can't get major %d\\n", hello_major);
		goto fail;
	

	hc_devp = kzalloc(sizeof(struct hello_char_dev)*hello_nr_devs,GFP_KERNEL);  //给字符设备分配空间,这里hello_nr_devs为2
	if(!hc_devp)
	
		printk(KERN_WARNING "alloc mem failed");
		ret = -ENOMEM;
		goto failure_kzalloc;		//内核常用goto处理错误
	

	for(i=0;i<hello_nr_devs;i++)	
	#if (LOCK_USE==0)
		sema_init(&hc_devp[i].sema,1);  //初始化信号量
	#elif (LOCK_USE==1)
		mutex_init(&hc_devp[i].mtx);   //初始化互斥量
	#endif

		cdev_init(&hc_devp[i].cdev,&hc_fops);		//初始化字符设备结构
		hc_devp[i].cdev.owner = THIS_MODULE;
		ret = cdev_add(&hc_devp[i].cdev,MKDEV(hello_major,hello_minor+i),1);
		if(ret)
		
			printk(KERN_WARNING"fail add hc_dev%d",i);
		
		
	
	hc_cls = class_create(THIS_MODULE,"hc_dev");
	if(!hc_cls)
	
		printk(KERN_WARNING"fail create class");
		ret = PTR_ERR(hc_cls);
		goto failure_class;
	
	for(i=0;i<hello_nr_devs;i++)
		device_create(hc_cls,NULL,MKDEV(hello_major,hello_minor+i),NULL,"hc_dev%d",i);
		
	printk(KERN_INFO "---END HELLO LINUX MODULE---\\n");
	return 0;

failure_class:
	kfree(hc_devp);
failure_kzalloc:		
	unregister_chrdev_region(devt,hello_nr_devs);
fail:
	return ret;	//返回错误,模块无法正常加载


//1111111111111111111111111111111111111111111111111111111111111111111
static void __exit hello_exit(void)

	int i;	
	for(i=0;i<hello_nr_devs;i++)
	
		device_destroy(hc_cls,MKDEV(hello_major,hello_minor+i));
	
	class_destroy(hc_cls);
	for(i=0;i<hello_nr_devs;i++)
		cdev_del(&hc_devp[i].cdev);
	kfree(hc_devp);
	unregister_chrdev_region(devt,hello_nr_devs);	//移除模块时释放设备号		
	printk(KERN_INFO "GOODBYE LINUX\\n");


//1111111111111111111111111111111111111111111111111111111111111111
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");//许可 GPL、GPL v2、Dual MPL/GPL、Proprietary(专有)等,没有内核会提示
MODULE_AUTHOR("KGZ");		//作者
MODULE_VERSION("V1.0");  	//版本


如下大写D表示进入一个不可中断的休眠状态。大写S是可中断休眠即用ctrl+c可中断掉。

如下等待条件满足,如上卡住的即休眠的进程会正常退出(两个进程cat卡住休眠,执行如下一行,两个进程都不会卡住,退出)。

2.内核表示时间和实现延时:编写驱动程序时,有时候要在一个命令和另一个命令之间有一个时间间隔,这时就需用到延迟操作

/*
linux中有一个时钟会周期性产生中断,linux将这中断作为时间基准。【中断频率】会保存在HZ这个变量里。【中断次数】会保存在jiffies变量里,可通过jiffies值获得到系统从开机到现在的时钟中断次数。常使用HZ和jiffies这两个变量构造各种时间函数。

HZ:100-1000之间。 jiffies: 系统时钟中断计数器。

在比较jiffies时使用下面的宏,可以避免32位系统溢出的问题:
time_after(a,b)		a>b?   a>b返回1,否则返回0
time_before(a,b)	a<b?
time_after_eq(a,b)	a>=b?
time_before_eq(a,b)	a<=b?

jiffies与常用时间之间的转换:
jiffies_to_msecs()    //毫秒
jiffies_to_usecs()    //微秒
msecs_to_jiffies()
usecs_to_jiffies() 
jiffies_to_timespec64()  //timespec结构里一个成员是秒,另一个是微妙
timespec64_to_jiffies()

延时:
wait_event_timeout()  //除了关心条件是否成立外,还关心超时是否到时,如果超时时间一到,不管条件是否满足,这两函数都会返回退出,然后执行后面操作。
wait_event_interruptible_timeout()
set_current_state()
schedule_timeout()  //单纯实现延时,不需要管条件,使用时需手动上行set_current_state()改变当前进程状态。

ndelay()  大于1000用下一个,均是忙等待即cpu死循环,一直占用cpu资源  //纳秒
udelay()   常用
mdelay()   如果用到了mdelay,那么你可能需要考虑使用msleep

休眠延时
usleep_range()   10us以上 20ms以下  
msleep()	毫秒延时
msleep_interruptible()
ssleep()    秒级延时
fsleep(unsigned long usecs)  新内核版本(5.13.2有) 微妙即以上所有延时。Documentation/timers/timers-howto.rst
*/
#include<linux/module.h>
#include<linux/jiffies.h>
#include<linux/sched.h>
#include<linux/delay.h>
unsigned long j,t1,diff;
struct timespec64 ts64;

//111111111111111111111111111111111111111111111111111111111111111111111
static int __init hello_init(void)	

	wait_queue_head_t wait;
	init_waitqueue_head(&wait);
	
	printk(KERN_INFO "HELLO LINUX MODULE\\n");
	j=jiffies;
	t1=j+msecs_to_jiffies(1000);  //+1秒延时(1000毫秒)
	printk(KERN_INFO "j=%ld t1=%ld\\n af:%d bf:%d afeq:%d beeq:%d\\n",j,t1,time_after(j,t1),time_before(j,t1),time_after_eq(j,t1),time_before_eq(j,t1));
	
	//忙等待,不推荐使用,浪费系统性能,有可能让系统进入死循环出不来(比如禁止了中断)
	printk(KERN_INFO "忙等待延时1s\\n");
	while(time_before(jiffies,t1))
	
	
	
	//等待队列延时
	printk(KERN_INFO "等待队列延时1s\\n");
	wait_event_interruptible_timeout(wait,0,msecs_to_jiffies(1000));
	
	//schedule_timeout延时
	printk(KERN_INFO "schedule_timeout延时1s\\n");
	set_current_state(TASK_INTERRUPTIBLE);
	schedule_timeout(msecs_to_jiffies(1000));
	
	//短延时函数
	printk(KERN_INFO "短延时\\n");
	mdelay(1000);
	
	//休眠延时
	printk(KERN_INFO "usleep_range延时\\n");
	usleep_range(10000,15000);
	printk(KERN_INFO "ssleep延时\\n");
	ssleep(1);
	
	diff = jiffies - j;  //获取如上中间一段几个延时差值
	printk(KERN_INFO"diff=%ld,time ms=%d us=%d\\n",diff,jiffies_to_msecs(diff),jiffies_to_usecs(diff)); //将jiffies差值转为毫秒和微妙值
	
	printk(KERN_INFO "系统开机到现在的时间:\\n");
	jiffies_to_timespec64(jiffies-INITIAL_JIFFIES,&ts64);
	printk(KERN_INFO"sec:%lld+ns:%ld\\n",ts64.tv_sec,ts64.tv_nsec);
	return 0;


//11111111111111111111111111111111111111111111111111111111111111111111111
static void __exit hello_exit(void)

	printk(KERN_INFO "GOODBYE LINUX\\n");

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");//许可 GPL、GPL v2、Dual MPL/GPL、Proprietary(专有)等,没有内核会提示
MODULE_AUTHOR("KGZ");		//作者
MODULE_VERSION("V1.0");  	//版本

3.内核实现延缓操作:内核定时器,tasklet,workqueue

有时希望一个事件在指定时间之后运行而不影响当前操作,或一个事件发生后,稍后去执行操作,不影响当前操作,这时需要内核为我们提供一些机制。如果想实现延迟指定时间之后执行指定操作,可使用内核定时器或workqueue工作队列。

如果想实现在一个事件发生之后再去执行其他操作,可使用tasklet或workqueue工作队列:这种情况多数发生在中断处理上,当一个中断发生后,在中断回调函数中希望尽可能短和尽可能快执行,所有把那些费时间的,不是很紧要的任务放稍后执行。

/*
1、内核定时器
struct timer_list
timer_setup()  //初始化上面结构并绑定回调函数
mod_timer()  //修改延时时间,调用后定时器开始定时,到达指定时间后调用绑定的回调函数
del_timer()  //删除定时器

2、tasklet  
struct tasklet_struct   
tasklet_init()   //绑定回调函数以及传入参数数据
tasklet_hi_schedule()   //让tasklet执行,带hi的对应回调函数在高优先级执行
tasklet_schedule()   //正常执行
tasklet_kill()

3、workqueue
alloc_workqueue()   //分配一个工作队列
destroy_workqueue()  

struct work_struct  //不需要延迟的操作
INIT_WORK()  //初始化工作
queue_work()  //将工作插入到工作队列中,内核自动调用对应工作

struct delayed_work  //需要延时的工作,先声明结构
INIT_DELAYED_WORK()  //初始化这结构
queue_delayed_work()   //将对应工作插入到对应工作队列中,内核会在指定定时时间后调用对应工作

4、其他
in_interrupt()   //指明当前上下文是不是在中断上下文,是在中断上下文,返回非0
smp_processor_id()  //返回当前运行所在cpu的id号

构造下面的数据表,通过设备读取 
// 第一列是jiffies表示的时间值。delta是两次获取之间的时间间隔。inirq是in_interrupt函数返回值,是否是在中断中。pid表明当前进程执行的pid。cpu是当前进程执行的cpu。command是当前进程的名称。
  time   delta  inirq    pid   cpu command   
4295601162    0     0      7559   0   cat
4295601162    0     1      10     0  ksoftirqd/0
4295601162    0     1      10     0  ksoftirqd/0
4295601162    0     1      10     0  ksoftirqd/0
4295601162    0     1      10     0  ksoftirqd/0
4295601162    0     1      10     0  ksoftirqd/0
./Documentation/core-api/workqueue.rst
*/
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/slab.h>
#include<linux/uaccess.h>
#include<linux/interrupt.h>  //包括了timer、tasklet和workqueue。
#include<linux/timer.h>		//定时器
#include<linux/workqueue.h>	//工作队列
#define DEFER_TEST 2  //(0:timer 1:tasklet 2:workqueue)
#define DELAY_WORK   //workqueue通过定义这行宏决定延时还是不延时
#define HELLO_MAJOR 0
#define HELLO_NR_DEVS 2

int hello_major = HELLO_MAJOR;
int hello_minor = 0;
dev_t devt;      //高12位是主设备号,低20位是次设备号
int hello_nr_devs = HELLO_NR_DEVS;

module_param(hello_major, int, S_IRUGO);
module_param(hello_minor, int, S_IRUGO);
module_param(hello_nr_devs, int, S_IRUGO);

//1111111111111111111111111111111111111111111111111111111111111111111111
struct hello_char_dev		//实际的字符设备结构,类似于面向对象的继承
	struct cdev cdev;
	char * buff;
	int loops;  //限制循环次数
	int tdelay;  //两次打印时间间隔
	unsigned 以上是关于Notes15进程休眠,时间和延时,延缓的主要内容,如果未能解决你的问题,请参考以下文章

C2内核模块,分配设备号,字符驱动,/设备节点,设备读写,/同步和互斥,ioctl函数,进程休眠,时间和延时,延缓,/proc文件系统,内存分配,数据类型,/内核中断,通过IO内存访问外设

markdown PHPExcel Notes和代码片段

python如何微秒级延时?

代码片段:Shell脚本实现重复执行和多进程

Linux系统信息与系统资源

取消Debian屏保及显示器休眠