在 Linux 内核编程中,I/O 操作通常需要直接访问硬件设备的寄存器。由于这些寄存器通常位于物理内存的特定位置,因此需要一种机制将物理地址映射到内核虚拟地址空间,以便内核代码可以安全地访问它们。ioremap 函数就是实现这一功能的关键工具之一。
1.ioremap
1.ioremap 函数
ioremap 函数的主要作用是将一段物理地址空间映射到内核虚拟地址空间,从而允许内核代码通过虚拟地址来访问硬件设备的寄存器或其他 I/O 设备。
函数原型:
static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
参数:
offset: 需要映射的物理地址。
size: 需要映射的字节数。
返回值:
虚拟地址,通过该地址可以访问指定的物理地址空间。
使用场景:
当驱动程序需要访问硬件设备的寄存器时,通常会使用 ioremap 将寄存器所在的物理地址映射到内核虚拟地址空间,然后通过虚拟地址进行读写操作。
示例代码:
#include <linux/io.h>
phys_addr_t phys_addr = 0x12345678; // 假设的物理地址unsigned long size = 0x1000; // 映射大小为4KB
void __iomem *virt_addr;
virt_addr = ioremap(phys_addr, size);
if (!virt_addr)
{
printk(KERN_ERR "ioremap failed\n");
return -ENOMEM;
}
// 现在可以通过 virt_addr 访问物理地址 0x12345678 对应的内存
2.iounmap 函数
与 ioremap 相对应,iounmap 用于取消虚拟地址到物理地址的映射。
函数原型:
void iounmap(volatile void __iomem *addr)
参数:
addr: 通过 ioremap 返回的虚拟地址。
使用场景:
当不再需要访问硬件设备的寄存器时,应使用 iounmap 取消映射,释放资源。
示例代码:iounmap(virt_addr);
3.I/O 访问函数
在映射了虚拟地址之后,内核提供了多种函数来读写不同宽度的数据。
writel 函数
函数原型:
static inline void writel(unsigned int b, volatile void __iomem *addr)
参数:
b: 要写入的值。
addr: 通过 ioremap 获得的虚拟地址。
功能:
将一个 32 位的值写入到指定的内存地址。
示例代码:
unsigned int value = 0x12345678;writel(value, virt_addr + 0x10); // 写入 virt_addr + 0x10 处
readl 函数
函数原型:
static inline unsigned int readl(const volatile void __iomem *addr)
参数:
addr: 通过 ioremap 获得的虚拟地址。
返回值:
从指定内存地址读取的 32 位值。
示例代码:
unsigned int value;
value = readl(virt_addr + 0x20); // 读取 virt_addr + 0x20 处的值
4.其他宽度的读写函数
除了 writel 和 readl,内核还提供了处理不同数据宽度的函数,例如:
writeb, readb: 8 位
writew, readw: 16 位
writeq, readq: 64 位
这些函数的使用方式与 writel 和 readl 类似,根据需要选择合适的数据宽度。
5.注意事项
内存屏障: 在某些体系结构上,直接的内存访问可能需要内存屏障来确保读写操作的顺序和可见性。
缓存一致性: 对于一些硬件设备,可能需要禁用缓存或使用特定的内存类型,以确保数据的一致性。
错误处理: ioremap 可能会失败,因此需要检查其返回值,确保映射成功后再进行读写操作。
2.中断
1.struct irq_desc 结构体
struct irq_desc 结构体定义在 irqdesc.h 中,用于描述一个中断描述符。主要成员包括:
struct irqaction *action;:这是一个链表头,用于保存中断处理程序的链表。每个中断可以有多个处理程序,这些处理程序通过 irqaction 结构体链接在一起。
2.struct irqaction 结构体
struct irqaction 结构体定义了一个中断处理程序的详细信息,主要成员包括:
irq_handler_t handler;:指向中断处理程序的指针。
void *dev_id;:一个指向设备特定数据的指针,通常用于区分多个设备共享同一个中断的情况。
struct irqaction *next;:指向下一个 irqaction 结构体,形成一个链表。
irq_handler_t thread_fn;:如果中断处理程序需要在单独的线程中执行,则这个成员指向线程函数。
struct task_struct *thread;:指向与中断相关的内核线程。
unsigned int irq;:中断号。
unsigned int flags;:中断标志,例如是否共享中断、触发方式等。
const char *name;:中断的名称,通常用于调试和显示。
struct proc_dir_entry *dir;:指向 /proc 文件系统中的条目,用于显示中断信息。
3.申请中断
request_irq 函数用于申请一个中断,并注册中断处理程序。函数的定义如下:
static inline int __must_checkrequest_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
irq:从资源获取的中断号。
handler:指定的中断处理程序。
flags:中断标志,例如中断的触发方式(边缘触发或电平触发)、是否共享中断等。
name:中断的名称,通常用于调试和显示。
dev:指向设备特定数据的指针,通常为 NULL。
返回值:成功时返回非负值,失败时返回负数错误码。
3. Tasklet
Tasklet 是一种轻量级的下半部机制,用于延迟执行中断处理程序的部分工作。
1.声明一个 Tasklet
DECLARE_TASKLET(my_tasklet, my_tasklet_func, 0);
my_tasklet:Tasklet 的名称。
my_tasklet_func:Tasklet 的处理函数,类型为 void (*func)(unsigned long)。
2.调度 Tasklet
tasklet_schedule(&my_tasklet);
tasklet_schedule:调度 Tasklet 在稍后执行。
4. 工作队列
工作队列是另一种下半部机制,适用于需要在独立线程中执行的任务。
1.定义一个工作队列
struct work_struct my_wq;
2.初始化工作队列
INIT_WORK(&my_wq, my_wq_func);
my_wq_func:工作队列的处理函数,类型为 void (*work_func_t)(struct work_struct *work)。
3.调度工作队列
schedule_work(&my_wq);
schedule_work:调度工作队列中的任务在稍后执行。
5. Jiffies
Jiffies 是 Linux 内核中的一个全局变量,表示系统启动以来的滴答数。(通常用于定时器)
1.定义一个定时器
struct timer_list my_timer;
2.初始化定时器
timer_setup() 是一个存在于 Linux 内核中的函数,通常用于设置内核定时器。它的定义和使用方式与用户空间中的定时器设置函数有所不同。
内核函数 timer_setup()
timer_setup() 是 Linux 内核中用于初始化定时器的函数。它的原型如下:
void timer_setup(struct timer_list *timer, void (*callback)(struct timer_list *), unsigned int flags);
参数说明:
timer:指向 struct timer_list 结构的指针,表示要设置的定时器。
struct timer_list 是内核中用于管理定时器的结构体。
callback:定时器到期时要执行的回调函数。
回调函数的原型必须是 void callback(struct timer_list *timer)。
flags:定时器的标志位,通常设置为 0。
init_timer(&my_timer);
3.设置超时函数和时间
my_timer.function = my_function;
my_timer.expires = jiffies + HZ;
my_function:定时器超时时要执行的函数,类型为 void (*function)(unsigned long)。
expires:定时器超时的时间点,通常设置为当前 jiffies 加上一个滴答数(HZ 表示每秒的滴答数)。
4.添加定时器
add_timer(&my_timer);
add_timer:将定时器添加到内核定时器链表中,定时器将在指定的时间点超时。
总结
中断:通过 request_irq 申请中断,并在 struct irqaction 中注册中断处理程序。
Tasklet:用于延迟执行中断处理程序的部分工作,调度后在稍后执行--不可sleep。
工作队列:适用于需要在独立线程中执行的任务,调度后在稍后执行--可sleep。
定时器:通过 jiffies 设置超时时间,定时器超时后执行指定的函数--。
这些机制在内核中广泛用于处理中断和延迟执行的任务。
408

被折叠的 条评论
为什么被折叠?



