任务:
度量时间差,如何比较时间
获得当前时间
将操作延迟指定一段时间
调度异步函数到指定时间之后执行


度量时间差

内核通过定时器中断来跟踪时间流。

时间中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据HZ的值设定,HZ是一个与体系结构有关的常数,定义在<linux/param.h>或者该文件下。

hz:上述间隔由hz的值设定,hz是一个与体系结构相关的常数
计数器:发生中断一次,计数器加一,这个计数器的值(只有)在系统引导时被初始化为0
jiffies变量:unsigned long 型变量,要么与jiffies_64相同,要么取其低32位


使用jiffies计数器

该计数器和读取计数器的工具函数包含在<linux/fiffies.h>,但是通常只需包含<linux/sched.h>文件,后者会自动放入jiffes.h。jiffies和jiffies_64均应被看成只读变量。

#include<linux/jiffies.h>
unsigned long j,stamp_1,stamp_half,stamp_n;
j=jiffies;  //read the current value
stamp_1=j+HZ;  //1second in the future
stamp_half=j+HZ/2;  //0.5second in the future
stamp_n=j+n*HZ/1000;  // n milliseconds

比较缓存值(例如上述的stamp_1)与当前值:

#include<linux/jiffies.h>
int time_after(unsigned long a,unsigned long b);
int time_before(unsigned long a,unsigned long b);
int time_after_eq(unsigned long a,unsigned long b);
int time _before_eq(unsigned long a,unsigned long b);

如果a所代表的时间比b靠后,则第一个宏返回真;否则,第二个宏返回真;后面两个宏分别用来比较“靠后或者相等”及“靠前或者相等”。这些宏会将计数器值转换为signed long,相减,然后比较结果。如果需要以安全的方式计算两个jiffies实例之间的差,如下:

diff = (long) t2 - (long) t1;

而通过下面的方法,可将两个jiffies的差转换为毫秒值:

msec = diff *1000/HZ;

用户空间和内核空间的时间表述方法的转换:
用户空间方法:timeval,timespec
内核空间方法:jiffies

#include<linux/time.h>struct timespec {time_t    tv_sec;        /* seconds */long    tv_nsec;    /* nanoseconds */
};struct timeval {time_t        tv_sec;        /* seconds */SUSEconds_t    tv_usec;    /* microseconds */
};unsigned long timespec_to_jiffies(struct timespec *value);
void jiffies_to_timespec(unsigned long jiffies,struct timespec *value);
unsigned long timeval_to_jiffies(struct timeval *value);
void jiffies_to _timeval(unsigned long jiffies,struct timeval *value);

读取64位jiffies:jiffies_64
32位处理器上,对64位值的访问不是原子的。在读取64位值的高32位及低32位值时,可能发生更新,从而获得错误的值。
因此使用get_jiffies_64函数完成适当的锁定。

#include<linux/jiffies.h>u64 get_jiffies_64(void);

处理器特定的寄存器

如果需要精度很高的计时,jiffies已不可满足需要,这时就引入了一种技术就是CPU包含一个随时钟周期不断递增的计数寄存器。这是完成高分辨率计时任务的唯一可靠途径。

1.不管该寄存器是否置0,我们都强烈建议不要重置它。
2.TSC:这是一个64位寄存器,记录CPU的时钟周期数,从内核空间和用户空间都可以读取它。

#include<asm/msr.h>
以下宏是与体系结构相关的,上述头文件是x86专用头文件

rdtsc(low32,high32);rdtscl(low32);rdtscll(var64);

第一个宏原子性的把64位变量读到两个32位的变量中。
第二个读取低32位,废弃高32位。
第三个把64值读到一个long long型变量中。

下面代码完成测量指令自身运行时间

unsigned long ini,end;
rdtscl(ini);
rdtscl(end);
printk("time lapse:%li\n",end-ini);

现提供一个与体系结构无关的函数,可以替代rdtsc

#include<linux/timex.h>
cycles_t get_cycles(void);

在各种平台上都可以使用这个函数,在没有时钟周期计数寄存器的平台上它总是返回0。cycles_t类型是能装入读取值的合适的无符号类型。

关于时间戳计数器,在SMP系统中,它不会再多个处理期间保持同步。为确保获得一致的值,我们需要为查询该计数器的代码禁止抢占。


获取当前时间

内核一般使用jiffies值来获取当前时间。该数值表示的是自最近一次系统启动到当前的时间间隔,与设备驱动程序无关,因为它的生命期只限于系统的运行期(uptime)。但驱动程序可以利用jiffies的当前值来计算不同事件间的时间间隔。

墙钟时间–>jiffies时间:

 #include<linux/time.h>unsigned long mktime(unsigned int year,unsigned int month,unsigned int day, unsigned int  hour,unsigned int  minute,unsigned int second);

为了处理绝对时间, <linux/time.h> 导出了 do_gettimeofday 函数,它填充一个指向 struct timeval 的指针变量。绝对时间也可来自 xtime 变量,一个 struct timespec 值,为了原子地访问它,内核提供了函数 current_kernel_time。它们的精确度由硬件决定,原型是:

 #include<linux/time.h>
void do_gettimeofday(structtimeval*tv);
struct timespec current_kernel_time(void);
/*得到的数据都表示当前时间距UNIX时间基准1970-01-01 00:00:00的相对时间*/

全局变量xtime:它是一个timeval结构类型的变量,用来表示当前时间距UNIX时间基准1970-01-01 00:00:00的相对秒数值。
xtime精度差,很难原子地访问timeval变量的两个成员,不鼓励使用。
因此,内核提供了一个辅助函数current_kernel_time:

#include<linux/time.h>
struct timespec current_kernel_time(void);

延迟执行

设备驱动程序经常需要将某些特定代码延迟一段时间后执行——通常是为了完成某些任务。


长延迟

长于一个时钟滴答。


忙等待

若想延迟执行若干个时钟嘀哒,精度要求不高。最容易的( 尽管不推荐 ) 实现是一个监视 jiffies 计数器的循环。

 while(time_before(jiffies, j1))cpu_relax();/*j1是延迟终止时的jiffies的值*/

对 cpu_relex 的调用将以体系相关的方式执行,在许多系统中它根本不做任何事,这个方法应当明确地避免。

这种忙等待严重地降低了系统性能。如果未配置内核为抢占式, 这个循环在延时期间完全锁住了处理器,计算机直到时间 j1 到时会完全死掉。如果运行一个可抢占的内核时会改善一点,但是忙等待在可抢占系统中仍然是浪费资源的。更糟的是, 当进入循环时如果中断碰巧被禁止, jiffies 将不会被更新, 并且 while 条件永远保持真,运行一个抢占的内核也不会有帮助, 唯一的解决方法是重启。

对于ARM体系来说:

 #define cpu_relax()            barrier()

也就是说在ARM上允许忙等待相当于:

while(time_before(jiffies, j1)) ;

让出处理器

忙等待加重了系统负载,必须找出一个更好的技术:不需要CPU时释放CPU 。 这可通过调用schedule函数实现(在 <linux/sched.h> 中声明):

 while(time_before(jiffies, j1)){schedule();
}

当前进程虽然释放了CPU而不做任何事情,但它任然在运行队列。如果系统只有一个可运行的进程,则该进程又会立刻执行(它调用了调度器,而调度器选择了同一进程,这个进程又调用了调度器…)


超时

实现延迟的最好方法应该是让内核为我们完成相应的工作。

1.若驱动使用一个等待队列来等待某些其他事件,并想确保它在一个特定时间段内运行,可使用:

#include<linux/wait.h>
long wait_event_timeout(wait_queue_head_t q, condition,long timeout);
long wait_event_interruptible_timeout(wait_queue_head_t q, condition,long timeout);
/*这些函数在给定队列上睡眠, 但是它们在超时(以 jiffies 表示)到后返回。
这里的timeout值表示的时要等待的jiffies值,而不是绝对值。
如果超时,函数返回 0; 如果这个进程被其他事件唤醒,则返回以 jiffies 表示的剩余的延迟实现;返回值从不会是负值*/

2.为了实现进程在超时到期时被唤醒而又不等待特定事件(避免声明和使用一个多余的等待队列头),内核提供了 schedule_timeout 函数:

#include<linux/sched.h>
signed long schedule_timeout(signedlong timeout);
/*timeout 是要延时的 jiffies 数。除非这个函数在给定的 timeout 流失前返回,否则返回值是 0 。schedule_timeout 要求调用者首先设置当前的进程状态。为获得一个不可中断的延迟, 可使用 TASK_UNINTERRUPTIBLE 代替。*/

如果你忘记改变当前进程的状态, 调用 schedule_time 如同调用 shcedule,建立一个不用的定时器。一个典型调用如下:

set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout (delay);

短延迟

当一个设备驱动需要处理硬件的延迟(latency潜伏期), 涉及到的延时通常最多几个毫秒,在这个情况下, 不应依靠时钟嘀哒。

内核函数 ndelay, udelay和 mdelay ,他们分别延后执行指定的纳秒数, 微秒数或者毫秒数,定义在 <asm/delay.h>,原型如下:

#include<linux/delay.h>
void ndelay(unsignedlong nsecs);
void udelay(unsignedlong usecs);
void mdelay(unsignedlong msecs);

延迟超过请求的值通常不是问题,因为驱动程序的短延迟通常等待的是硬件,而需求往往是至少要等待给定的时间段。

重要的是记住这 3 个延时函数是忙等待; 其他任务在时间流失时不能运行。每个体系都实现 udelay, 但是其他的函数可能未定义; 如果它们没有定义, <linux/delay.h> 提供一个缺省的基于 udelay 的版本。在所有的情况中, 获得的延时至少是要求的值, 但可能更多。udelay 的实现使用一个软件循环, 它基于在启动时计算的处理器速度和使用整数变量 loos_per_jiffy确定循环次数。

为避免在循环计算中整数溢出, 传递给udelay 和 ndelay的值有一个上限,如果你的模块无法加载和显示一个未解决的符号:__bad_udelay, 这意味着你调用 udleay时使用太大的参数。
作为一个通用的规则:若试图延时几千纳秒, 应使用 udelay 而不是 ndelay; 类似地, 毫秒规模的延时应当使用 mdelay 完成而不是一个更细粒度的函数。

有另一个方法获得毫秒(和更长)延时而不用涉及到忙等待的方法是使用以下函数(在<linux/delay.h> 中声明):

void msleep(unsignedint millisecs);
unsigned long msleep_interruptible(unsignedint millisecs);
void ssleep(unsignedint seconds)

若能够容忍比请求的更长的延时,应使用 schedule_timeout, msleep 或 ssleep。


内核定时器

当需要调度一个以后发生的动作, 而在到达该时间点时不阻塞当前进程, 则可使用内核定时器。内核定时器用来调度一个函数在将来一个特定的时间(基于时钟嘀哒)执行,从而可完成各类任务。

内核定时器是一个数据结构, 它告诉内核在一个用户定义的时间点使用用户定义的参数执行一个用户定义的函数,函数位于 <linux/timer.h> 和 kernel/timer.c 。被调度运行的函数几乎确定不会在注册它们的进程在运行时运行,而是异步运行。实际上, 内核定时器通常被作为一个"软件中断"的结果而实现。当在进程上下文之外(即在中断上下文)中运行程序时, 必须遵守下列规则:

1.不允许访问用户空间;
2.current 指针在原子态没有意义;
3.不能进行睡眠或者调度. 例如:调用 kmalloc(…, GFP_KERNEL) 是非法的,信号量也不能使用因为它们可能睡眠。

通过调用函数 in_interrupt()能够告知是否它在中断上下文中运行,无需参数并如果处理器当前在中断上下文运行就返回非零。
通过调用函数 in_atomic()能够告知调度是否被禁止,若调度被禁止返回非零; 调度被禁止包含硬件和软件中断上下文以及任何持有自旋锁的时候。

在后一种情况, current 可能是有效的,但是访问用户空间是被禁止的,因为它能导致调度发生. 当使用 in_interrupt()时,都应考虑是否真正该使用的是 in_atomic 。他们都在 <asm/hardirq.h> 中声明。

内核定时器的另一个重要特性是任务可以注册它本身在后面时间重新运行,因为每个 timer_list 结构都会在运行前从激活的定时器链表中去连接,因此能够立即链入其他的链表。一个重新注册它自己的定时器一直运行在同一个 CPU.

即便在一个单处理器系统,定时器是一个潜在的态源,这是异步运行直接结果。因此任何被定时器函数访问的数据结构应当通过原子类型或自旋锁被保护,避免并发访问。


定时器API

内核提供给驱动许多函数来声明、注册以及删除内核定时器:

#include <linux/timer.h>
struct timer_list {struct list_head entry;unsigned long expires;/*期望定时器运行的绝对 jiffies 值,不是一个 jiffies_64 值,因为定时器不被期望在将来很久到时*/void (*function)(unsigned long); /*期望调用的函数*/unsigned long data;/*传递给函数的参数,若需要在参数中传递多个数据项,可以将它们捆绑成单个数据结构并且将它的指针强制转换为 unsiged long的指针传入。这种做法在所有支持的体系上都是安全的并且在内存管理中相当普遍*/struct tvec_t_base_s *base;
#ifdef CONFIG_TIMER_STATSvoid *start_site;char start_comm[16];int start_pid;
#endif
};
/*这个结构必须在使用前初始化,以保证所有的成员被正确建立(包括那些对调用者不透明的初始化):*/
void init_timer(struct timer_list *timer);
struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
/*在初始化后和调用 add_timer 前,可以改变 3 个公共成员:expires、function和data*/
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);/*在到时前禁止一个已注册的定时器*/
int del_timer_sync(struct timer_list *timer); /*如同 del_timer ,但还保证当它返回时, 定时器函数不在任何 CPU 上运行,以避免在 SMP 系统上竞态, 并且在单处理器内核中和 del_timer 相同。这个函数应当在大部分情况下优先考虑。 如果它被从非原子上下文调用, 这个函数可能睡眠,但是在其他情况下会忙等待。当持有锁时要小心调用 del_timer_sync ,如果这个定时器函数试图获得同一个锁, 系统会死锁。如果定时器函数重新注册自己, 调用者必须首先确保这个重新注册不会发生; 这通常通过设置一个" 关闭 "标志来实现, 这个标志被定时器函数检查*/
int mod_timer(struct timer_list *timer, unsigned long expires); /*更新一个定时器的超时时间, 常用于超时定时器。也可在正常使用 add_timer时在不活动的定时器上调用mod_timer*/
int timer_pending(const struct timer_list * timer); /*通过调用timer_list结构中一个不可见的成员,返回定时器是否在被调度运行*/

内核定时器的实现

内核定时器的实现需满足:
1.定时器的管理必须尽可能做到轻量级。
2.其设计必须在活动定时器大量增加时具有很好的伸缩性。
3.大部分定时器最多几秒或几分钟内到期,而很少存在长期延迟的定时器。
4.定时器应该在注册它的同一CPU上运行。

定时器会在正确的时间到期,即使我们运行的不是抢占式的内核,而CPU会忙于内核空间。


tasklet

tasklet vs 内核定时器
相同 始终在中断期间运行,始终在调度它们的同一CPU上运行,而且都接收一个unsigned long参数
不同 不可以要求tasklet在某以给定的时间执行

tasklet对中断处理例程来说尤其有用。中断处理例程必须尽可能快的管理硬件中断,而大部分数据管理则可以安全的延迟到其后的时间。

实际上,与内核定时器类似,tasklet也会在“软件中断”上下文以原子模式执行。软件中断指打开硬件中断的同时执行某些异步任务的内核机制。

tasklet以数据结构形式存在,并在使用前必须初始化。调用特定的函数或者使用特定的宏来声明该结构,即可完成tasklet的初始化:

 #include<linux/interrupt.h>struct tasklet_struct{/*......*/void(*func)(unsigned long);unsigned long data;
};void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }void tasklet_disable(struct tasklet_struct *t);
/*函数暂时禁止给定的 tasklet被 tasklet_schedule 调度,直到这个 tasklet 被再次被enable;若这个 tasklet 当前在运行, 这个函数忙等待直到这个tasklet退出*/void tasklet_disable_nosync(struct tasklet_struct *t);
/*和tasklet_disable类似,但是tasklet可能仍然运行在另一个 CPU */void tasklet_enable(struct tasklet_struct *t);
/*使能一个之前被disable的 tasklet;若这个 tasklet 已经被调度, 它会很快运行。 tasklet_enable 和tasklet_disable必须匹配调用, 因为内核跟踪每个 tasklet 的"禁止次数"*/void tasklet_schedule(struct tasklet_struct *t);
/*调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行; 这保证了在其他事件被处理当中发生的事件受到应有的注意. 这个做法也允许一个 tasklet 重新调度它自己*/void tasklet_hi_schedule(struct tasklet_struct *t);
/*和tasklet_schedule类似,只是在更高优先级执行。当软中断处理运行时, 它处理高优先级 tasklet 在其他软中断之前,只有具有低响应周期要求的驱动才应使用这个函数, 可避免其他软件中断处理引入的附加周期*/void tasklet_kill(struct tasklet_struct *t);
/*确保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet 正在运行, 这个函数等待直到它执行完毕。若 tasklet 重新调度它自己,则必须阻止在调用 tasklet_kill 前它重新调度它自己,如同使用 del_timer_sync*/

tasklet特性:
1.一个tasklet可在稍后被禁止或者重新启用;只有启用的次数和禁止的次数相同时,tasklet才会被执行。
2.和定时器类似,tasklet可以自己注册自己。
3.tasklet可被调度以在通常的优先级或者高优先级执行。高优先级的tasklet总会优先执行。
4.如果系统负荷不重,则tasklet会立即执行,但始终不会晚于下一个定时器滴答。
5.一个tasklet可以和其它tasklet并发,但对自身来讲是严格串行处理的,也就是说,同一tasklet永远不会在多个处理器上同时运行:tasklet始终会调度自己在同一CPU上运行。


工作队列

表面来看,工作队列类似于tasklet:允许内核代码请求某个函数在将来的时间被调用。

但其实还是有很多不同:
1.tasklet在软中断上下文中运行,因此,所有的tasklet代码都是原子的。相反,工作队列函数在一个特殊的内核进程上下文中运行,因此他们有更好的灵活性。尤其是,工作队列可以休眠!
2.tasklet始终运行在被初始提交的统一处理器上,但这只是工作队列的默认方式
3.内核代码可以请求工作队列函数的执行延迟给定的时间间隔

两者的关键区别:tasklet会在很短的时间内很快执行,并且以原子模式执行,而工作队列函数可以具有更长的延迟并且不必原子化。两种机制有各自适合的情形。

工作队列有 struct workqueue_struct 类型,在 <linux/workqueue.h> 中定义。一个工作队列必须明确的在使用前创建,宏为:

struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);

每个工作队列有一个或多个专用的进程(“内核线程”), 这些进程运行提交给这个队列的函数。 若使用 create_workqueue, 就得到一个工作队列它在系统的每个处理器上有一个专用的线程。在很多情况下,过多线程对系统性能有影响,如果单个线程就足够则使用 create_singlethread_workqueue 来创建工作队列。

/*需要填充work_struct或delayed_work结构,可以在编译时完成, 宏如下: */struct work_struct {atomic_long_t data;
#define WORK_STRUCT_PENDING 0        /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)struct list_head entry;work_func_t func;
};struct delayed_work {struct work_struct work;struct timer_list timer;
};DECLARE_WORK(n, f)
/*n 是声明的work_struct结构名称, f是要从工作队列被调用的函数*/
DECLARE_DELAYED_WORK(n, f)
/*n是声明的delayed_work结构名称, f是要从工作队列被调用的函数*//*若在运行时需要建立 work_struct 或 delayed_work结构, 使用下面 2 个宏定义:*/
INIT_WORK(struct work_struct *work, void (*function)(void *));
PREPARE_WORK(struct work_struct *work, void (*function)(void *));
INIT_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));
PREPARE_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));
/* INIT_* 做更加全面的初始化结构的工作,在第一次建立结构时使用. PREPARE_* 做几乎同样的工作, 但是它不初始化用来连接 work_struct或delayed_work 结构到工作队列的指针。如果这个结构已经被提交给一个工作队列, 且只需要修改该结构,则使用 PREPARE_* 而不是 INIT_* *//*有 2 个函数来提交工作给一个工作队列:*/
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct delayed_work *work, unsigned long delay);
/* 每个都添加work到给定的workqueue。如果使用 queue_delay_work, 则实际的工作至少要经过指定的 jiffies 才会被执行。 这些函数若返回 1 则工作被成功加入到队列; 若为0,则意味着这个 work 已经在队列中等待,不能再次加入*/

在将来的某个时间, 这个工作函数将被传入给定的 data 值来调用。这个函数将在工作线程的上下文运行, 因此它可以睡眠 (你应当知道这个睡眠可能影响提交给同一个工作队列的其他任务) 工作函数不能访问用户空间,因为它在一个内核线程中运行, 完全没有对应的用户空间来访问。
取消一个挂起的工作队列入口项可以调用:

int cancel_delayed_work(struct delayed_work *work);
void cancel_work_sync(struct work_struct *work)

如果这个入口在它开始执行前被取消,则返回非零。内核保证给定入口的执行不会在调用 cancel_delay_work 后被初始化. 如果 cancel_delay_work 返回 0, 但是, 这个入口可能已经运行在一个不同的处理器, 并且可能仍然在调用 cancel_delayed_work 后在运行. 要绝对确保工作函数没有在 cancel_delayed_work 返回 0 后在任何地方运行, 你必须跟随这个调用来调用:

void flush_workqueue(struct workqueue_struct *queue);

在 flush_workqueue 返回后, 没有在这个调用前提交的函数在系统中任何地方运行。
而cancel_work_sync会取消相应的work,但是如果这个work已经在运行那么cancel_work_sync会阻塞,直到work完成并取消相应的work。

当用完一个工作队列,可以去掉它,使用:

void destroy_workqueue(struct workqueue_struct *queue);

共享队列

在许多情况下, 设备驱动不需要它自己的工作队列。如果你只偶尔提交任务给队列, 简单地使用内核提供的共享的默认的队列可能更有效。若使用共享队列,就必须明白将和其他人共享它,这意味着不应当长时间独占队列(不能长时间睡眠), 并且可能要更长时间才能获得处理器。

步骤:
1.建立 work_struct 或 delayed_work

static struct work_struct jiq_work;
static struct delayed_work jiq_work_delay;/* this line is in jiq_init() */
INIT_WORK(&jiq_work, jiq_print_wq);
INIT_DELAYED_WORK(&jiq_work_delay, jiq_print_wq);

2.提交工作

int schedule_work(&jiq_work);/*对于work_struct结构*/
int schedule_delayed_work(&jiq_work_delay, delay);/*对于delayed_work结构*//*返回值的定义和 queue_work 一样*/

若需取消一个已提交给工作队列入口项, 可以使用 cancel_delayed_work和cancel_work_sync, 但刷新共享队列需要一个特殊的函数:

void flush_scheduled_work(void);

因为不知道谁可能使用这个队列,因此不可能知道 flush_schduled_work 返回需要多长时间。

《Linux Device Driver》——时间、延迟及延缓操作相关推荐

  1. 【Linux 驱动】第七章 时间 延迟及延缓操作

    一,度量时间差 时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据 HZ 值来设定,HZ 是一个体系依赖的值,在 <linux/param.h>中定义或该文件包含的某个子平台相关 ...

  2. Linux中SDIO命令,linux device driver之sdio驱动编程分享

    linux device driver之sdio驱动编程分享 闯客网 • 2018-12-19 • 技术交流 [p=26, null, left]先谈谈如何写linux驱动:[/p]- 在驱动模块初始 ...

  3. Linux设备驱动程序学习(10)-时间、延迟及延缓操作(Jiq.c)

    共享隊列 点击(此处)折叠或打开 /* * jiq.c -- the just-in-queue module * * Copyright (C) 2001 Alessandro Rubini and ...

  4. linux设备驱动七(时间、延迟及延缓操作)

    知识点: 如何度量时间差,如何比较时间 如何获得当前时间 如何将操作延迟指定一段时间 如何调度异步函数到指定时间之后执行 度量时间差 HZ指一秒内产生的时钟中断次数,即时钟中断频率 jiffies_6 ...

  5. 时间、延迟及延缓操作

    现实中的设备驱动程序除了必需的操作之外还要做更多工作,如定时.内存管理.硬件访问等,现在来看看内核是如何对时间进行处理的. 关于时钟的一些说明:一台装有操作系统的计算机里一般有两个时间:硬件时钟和软件 ...

  6. 时间、延迟以及延缓操作

    HZ和jiffies 内核通过定时器中断来跟踪时间流.时钟中断由系统定时器一周期性的间隔产生,这个间隔有内核根据HZ的值决定.HZ是一个与体系结构有关的常数,定义在<linux/param.h& ...

  7. [Linux Device Driver] 看门狗

    0. 背景&&原理 我们假设有一个芯片,它自己能不间断的产生脉冲信号(隔一段时间产生,时间由硬件决定),它理所当然有自己的信号时序:比如说,它在每次脉冲前面都要有一个别的信号来通知它, ...

  8. Linux设备驱动模型概述(Linux device driver model overview)

    平台(platform)设备和平台驱动是连接到平台总线(bus)的linux驱动模型接口,总线将设备和驱动绑定,这个伪总线(platform bus)以最小的基础结构被用来连接设备到总线上.在系统每注 ...

  9. 修改linux系统的时间PRC,Ubuntu16.04操作系统环境下修改时区

    本文主要介绍如何在Ubuntu16.04操作系统环境下修改时区. 1. 时区相关操作 如果你的Linux系统时区配置不正确,必须要手动调整到正确的当地时区.NTP对时间的同步处理只计算当地时间与UTC ...

最新文章

  1. wattosR6中文化步骤
  2. xshell连接服务器失败_xshell-ssh连接服务器被经常意外中断
  3. tp5数组为什么要中括号_VBA数组与字典解决方案第7讲:为什么要采用数组公式(一)...
  4. 【POJ1821】Fence
  5. Web前端开发入门之网页制作三要素!
  6. 如何给女朋友解释什么是撞库、脱库和洗库?
  7. Markdown编辑器常用功能以及快捷键介绍
  8. Android优化方案之--Fragment的懒加载实现
  9. 服务器linux什么镜像好用,Linux系统镜像建站Debian和Ubuntu选择哪个比较好
  10. Android计算器效果截图,Android复杂计算器实现
  11. Linux安装yum工具-安装过程
  12. PHP实现队列(二)Redis 实现队列
  13. object与reflect
  14. 软件调试实战(软件调试权威指南)
  15. 国防科技大学 educoder C语言答案(湖南工业大学)
  16. ardupilot 关于设备车Rover的学习《1》------如何编译下载
  17. IDC:中国云计算市场超10亿 企业云火热
  18. 权威发布丨2020 中国开源先锋 33 人之心尖上的开源人物
  19. Win10系统重装 华硕笔记本电脑
  20. 清华大学计算机与科学分数线,清华大学各地录取情况及调档线一览

热门文章

  1. windows远程提示credssp加密数据库修正问题解决
  2. 关于Johnson-Trotter和字典序排列在《算法设计与分析基础》中的论述
  3. JAVA2EE 十三大规范
  4. 设计师必备的30款好看的手写字体免费下载
  5. python十个实战项目
  6. python虚拟机原理_pvm虚拟机基本原理
  7. 【九九归一|算法强化】HOT 算法①
  8. nginx cache 总结
  9. The security economics of large-scale attacks against Internet-connected ICS devices
  10. 准确率、精度和召回率