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内存访问外设