LDD3学习9--数据类型和定时器
这部分对应的是第七章和第十一章,因为内容也不是很多,就一起写了。里面的内容基本上就是一个个的点,所以也就一个个点简单总结一下。
1 数据类型
1.1 数据长度
不同操作系统类型长度可能不一样,看图的话最好用u8,u16,u32,u64。
内存页最好使用PAGE_SIZE,而不要使用4K,因为很多平台可能不是4K。
1.2 字节序大小端
这个在网络编程涉及到也很多,用的时候查一下就知道了。
1.3 数据对齐
书里面说的是最后加一个__attribute__ ((packed)) scsi;,这个是取消对齐,不过我记得在一般常用的时候,都是手动指定对齐:
struct BitFieldStruct {
unsigned int a : 4; // 占用 4 位
unsigned int b : 3; // 占用 3 位
unsigned int c : 1; // 占用 1 位
};
具体用的时候再看吧。。
1.4 判断指针
不要用NULL,用ERR_PTR,IS_ERR,PTR_ERR。
1.5 链表
这个不用自己搞,用内核里面的<linux/list.h>
2 定时器
内核通过定时器中断来跟踪时间的流动,大部分平台运行在 100 或者 1000 中断每秒; 流行的 x86 PC 缺省是 1000。
2.1 定时器
一般用的是 jiffies定时器,是在<linux/jiffies.h>。用法就不多写了,要用的时候搜一下或者GPT,答案都很标准。
高进度的定时器,可以用TSC,例子是:
unsigned long ini, end;
rdtscl(ini); rdtscl(end);
printk("time lapse: %li\n", end - ini);
两者的区别:
特性 | TSC 定时器 | jiffies 定时器 |
---|---|---|
依赖硬件 | 依赖 CPU 硬件支持 | 不依赖硬件,完全由内核实现 |
精度 | 纳秒级 | 毫秒级 |
性能 | 非常高效 | 开销较小,但需依赖时钟中断 |
多核一致性 | 可能存在问题 | 无多核一致性问题 |
功耗 | 较高(高频访问可能增加功耗) | 较低(只在时钟中断时更新) |
时间跨度 | 通常不适合长时间跨度 | 可用于长时间跨度(只需考虑溢出) |
典型场景 | 高精度时间戳,性能测量,延迟计算 | 调度、内核延迟、一般计时需求 |
2.2 当前时间
获取当前时间,时间戳,这些和应用层好像差不多,就不多说了。
延迟,在应用层,基本上就是一个sleep打天下。在内核好像东西多了不少。
long wait_event_interruptible_timeout(wait_queue_head_t *q, condition, signed long timeout);这个是用在条件变量。
signed long schedule_timeout(signed long timeout);这个会让当前任务进行休眠,但是会被唤醒,比如信号量。
例子:
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/delay.h>
void example_function(void) {
long timeout = msecs_to_jiffies(100); // 将100毫秒转换为jiffies
set_current_state(TASK_INTERRUPTIBLE); // 设置当前任务状态为可中断睡眠
schedule_timeout(timeout); // 让当前任务睡眠指定的时间
}
ndelay,udelay,mdelay。这几个都是让CPU空转,会占用很多CPU资源。所以只能用在短时间。
msleep。基于调度器调度,不会占用太多CPU资源。
2.3 内核定时器
定义是在<linux/timer.h>,让内核在指定时间后执行某个任务,某个事件或函数。通过timer_list结构,使用 init_timer() 或 timer_setup() 初始化定时器。 使用 add_timer() 启动定时器。 使用 del_timer() 删除定时器。
例子:
void setup_my_timer(void) {
timer_setup(&my_timer, my_timer_callback, 0);
my_timer.expires = jiffies + msecs_to_jiffies(1000); // 设置定时1秒
add_timer(&my_timer);
}
2.4 Tasklets机制
Tasklets 是 Linux 内核中一种轻量级的底半部(Bottom Half)机制,专门用于在软中断(SoftIRQ)上下文中执行延迟处理任务。它可以延迟执行某些非时间敏感的任务,而不会阻塞中断处理程序(Top Half)。
这里有一个例子:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple Tasklet Example");
// Tasklet 函数
void my_tasklet_func(unsigned long data) {
printk(KERN_INFO "Tasklet executed! Data: %lu\n", data);
}
// 定义 Tasklet,初始化时指定执行函数和参数
DECLARE_TASKLET(my_tasklet, my_tasklet_func, 42);
// 模块加载时调用
static int __init tasklet_example_init(void) {
printk(KERN_INFO "Tasklet example module loaded.\n");
// 调度 Tasklet
tasklet_schedule(&my_tasklet);
printk(KERN_INFO "Tasklet scheduled.\n");
return 0;
}
// 模块卸载时调用
static void __exit tasklet_example_exit(void) {
// 确保 Tasklet 在卸载前被销毁
tasklet_kill(&my_tasklet);
printk(KERN_INFO "Tasklet example module unloaded.\n");
}
module_init(tasklet_example_init);
module_exit(tasklet_example_exit);
运行结果如下:
[553563.141606] Tasklet example module loaded. //insmod
[553563.141615] Tasklet scheduled.
[553563.141618] Tasklet executed with data: 42
[553595.066381] Tasklet example module unloaded. //rmmod
看看代码就基本明白,tasklet其实就是将任务提交给CPU调度。比如说收到一个网络包,中断处理中收到包,之后还有繁琐的解包操作,如果还是占用中断,会阻塞其它任务。这个是可以调用tasklet来处理,不用全部占用CPU,提高系统整体性能。
这样中断的前半部分就是硬中断,后半部分就是软中断,tasklet这些。
例子:
irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
// 检查中断源是否来自预期设备
if (check_device_irq(irq)) {
// 读取设备状态寄存器
unsigned int status = read_device_status();
// 清除中断标志
clear_device_irq(irq);
// 做一些简单的处理,如将数据从设备缓冲区拷贝到内存的临时位置
copy_data_from_device();
// 触发中断下半部处理
schedule_delayed_work(&my_work, msecs_to_jiffies(10));
return IRQ_HANDLED;
}
return IRQ_NONE;
}
2.5 工作队列
在#include <linux/workqueue.h>中。create_workqueue,create_singlethread_workqueue,DECLARE_WORK,INIT_WORK,PREPARE_WORK。。。
说实话之前也没用过这个,查了一下,本质上就是优先级更低的tasklet,可以被内核调度,适用于耗时更长,可以阻塞的任务。不过貌似现在要被kthread替代了。
最后:之前知乎看到一篇写定时器的,写的哇塞:C/C++中如何稳定地每隔5ms执行某个函数?
原文地址:https://blog.csdn.net/fanged/article/details/145229683
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!