1 container_of宏
1.1 简介
- 已知某结构体成员的地址,根据成员的地址去找出结构体的地址
1.2 API
#include <linux/kernel.h>
/**
* container_of - cast a member of a structure out to the containing structure
*
* @ptr: 结构体成员地址
* @type: 结构体类型
* @member: 结构体成员名字
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
1.3 示例
struct ipstore{
unsigned long time;
__u32 addr[4];
struct list_head list;
};
void container_of_test()
{
struct ipstore ip1;
struct ipstore *p1;
p1 = container_of(&ip1.list, struct ipstore, list);
printf("ip1's addr:0x%0x\n", &ip1);
printf("p1's addr:0x%0x\n", p1);
}
[root@xxx c_base]# ./a.out
ip1's addr:0xa5fe1fe0
p1's addr:0xa5fe1fe0
2 链表
2.1 简介
- 假设某个驱动程序里面一共管理了5个设备,因此该驱动程序需要实时监控这5个设备的执行状态,故而需要引入链表
- 链表实际上有单链表与双链表
- 然而linux内核里面已经实现了双链表,因为双链表能够实现FIFO与LIFO,该链表结构保存在了
<linux/list.h>
中
- 内核中链表实现的核心部分数据结构如下
struct list_head {
struct list_head *next, *prev;
};
2.2 API
#include <linux/list.h>
/*
功能:动态创建初始化链表
参数:
@list:实例化的链表对象
*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
/* 初始化链表示例 */
struct list_head mylist;
INIT_LIST_HEAD(&mylist);
/* 静态创建初始化链表 */
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
LIST_HEAD_INIT(name) (&(name), &(name))
- 创建链表节点:要新建节点,只需要创建数据结构实例,初始化嵌入在其中的list_head字段
/* 创建链表节点示例 */
struct car {
int door_number;
char *color;
char *module;
struct list_head list;
};
struct car *blackcar = kmalloc(sizeof(struct car), GFP_KERNEL);
/* 动态初始化链表 */
INIT_LIST_HEAD(&blackcar->list);
/*
功能:添加链表节点(头插法)
参数:
@new:新建的节点
@head:要插入的链表头节点地址
*/
void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next)
{
next->prev = new;
new->next = next;
next->prev = prev;
next->next = new;
}
/*
功能:添加链表节点(尾插法)
参数:
@new:新建的节点
@head:要插入的链表头节点地址
*/
void list_add_tail(struct list_head *new, struct list_head *head);
/*
功能:删除链表中的一个节点
参数:
@entry:链表中的某个节点
*/
void list_del(struct list_head *entry);
/*
功能:链表遍历
参数:
@pos:用于迭代,是一个循环游标,类似于for(i = 0;;)中的i
@head:链表头节点
@member:数据结构中链表struct list_head的名字
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/* 示例 */
struct car *acar;
int blue_car_num = 0;
list_for_each_entry(acar, carlist, list) {
if (acar->color == "blue")
blue_car_num++;
}
2.3 示例
/* 汽车类 */
struct car {
int door_number;
char *color;
char *module;
struct list_head list;
};
/* 汽车链表头部 */
static LIST_HEAD(carlist);
/* new一些汽车 */
struct car *redcar = kmalloc(sizeof(struct car), GPF_KERNEL);
struct car *bluecar = kmalloc(sizeof(struct car), GPF_KERNEL);
/* 初始化每个汽车节点 */
INIT_LIST_HEAD(&redcar->list);
INIT_LIST_HEAD(&bluecar->list);
/* 填充汽车属性(忽略) */
/* 将汽车对象添加到链表中 */
list_add(&redcar->list, &carlist);
list_add(&bluecar->list, &carlist);
/* 将红车从链表中移除 */
list_del(&redcar->list);
/* 释放红车对象内存 */
kfree(redcar);
/* 遍历汽车链表 */
struct car *acar;
int blue_car_num = 0;
list_for_each_entry(acar, carlist, list) {
if (acar->color == "blue")
blue_car_num++;
}
3 内核休眠
3.1 简介
- 即将内核进程阻塞直到进程获取了资源
- 在linux设备驱动中阻塞进程可以使用等待队列来实现
- 在内核中,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合,可以使用等待队列实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,同步对系统资源的访问
3.2 API
struct __wait_queue_head {
spinlock_t lock; //自旋锁,用来对task_list链表起保护作用,实现了等待队列的互斥访问
struct list_head task_list; //用来存放等待的进程,即上述的链表
};
typedef struct __wait_queue_head wait_queue_head_t;
#include <linux/wait.h>
#include <linux/sched.h>
/* 定义并初始化等待队列(动态) */
wait_queue_head_t wait;
init_waitqueue_head(&wait);
/* 定义并初始化等待队列(静态) */
#define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
/* 添加等待队列,将等待队列元素wait添加到等待队列队头q所指向的等待队列链表中 */
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
/* 移除等待队列 */
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
/* 等待事件,在等待队列中睡眠直到condition为真 */
/* 注意:
@queue:作为等待队列头的等待队列被唤醒
@condition:必须为真,否则就阻塞
@timeout:相较于condition,timeout有更高的优先级
*/
wait_event(wq, condition);
wait_evevt_timeout(wq, condition, timeout);
/* wait_event_interruptible不会持续去轮询判断条件是否满足,而只是在其被调用的时候才会去判断条件是否成立,如果条件为假进程将进入TASK_INTERRUPTIBLE并从运行队列中删除。啥时候被调用呢?只有当调用wake_up_interruptible唤醒函数的时候才会去检查条件,当条件为真的时候,进程状态将会被设置为TASK_RUNNING */
wait_event_interruptible(wq, condition);
wait_event_interruptible_timeout(wq, condition, timeout);
/* 睡眠,其中sleep_on作用是将目前进程的状态设置为TASK_UNINTERRUPTIBLE,直到资源可用,q引导的等待队列被唤醒;
interruptible_sleep_on是将进程状态设置为TASK_INTERRUPTIBLE
*/
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
/* 唤醒等待队列,可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程 */
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
/* 只能唤醒处于TASK_INTERRUPTIBLE状态的进程 */
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
3.3 示例
- 驱动示例,首先定义一个条件变量,之后初始化一个工作队列。在模块加载的时候回调函数
work_handler
添加到工作队列中,之后会执行wait_event_interruptible
函数,显然此刻条件为假,故而进程进入睡眠。在回调函数中睡眠5秒后将条件变量置为1,之后使用wake_up_interruptible
唤醒进程,进程再去执行wait_event_interruptible
函数,此刻条件成立,故主进程将被唤醒
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/time.h>
static DECLARE_WAIT_QUEUE_HEAD(my_wq);
static int condition = 0;
/* declare a work queue */
static struct work_struct wrk;
static void work_handler(struct work_struct *work)
{
printk(KERN_INFO "enter %s\n", __func__);
msleep(5000);
printk(KERN_INFO "wake up the sleeping moudle");
condition = 1;
wake_up_interruptible(&my_wq);
}
static int __init my_init(void)
{
printk(KERN_INFO "wait queue example init\n");
/* init work queue */
INIT_WORK(&wrk, work_handler);
/* commit our handler function to work queue */
schedule_work(&wrk);
printk(KERN_INFO "going to sleep %s\n", __func__);
/* wait for wake up job*/
wait_event_interruptible(my_wq, condition != 0);
printk(KERN_INFO "worken up by the work job\n");
return 0;
}
static void __exit my_exit(void)
{
printk(KERN_INFO "wait queue example exit\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Qi Han <15023820769@163.com");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/wait_qu
eue$ sudo insmod wait_queue.ko
[sudo] hq 的密码:
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/wait_qu
eue$ sudo rmmod wait_queue
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/wait_queue$ dmesg -wH
[ +0.000625] wait queue example init
[ +0.000003] going to sleep my_init
[ +0.000004] enter work_handler
[ +5.228811] wake up the sleeping moudle
[ +0.000059] worken up by the work job
[ +13.778234] wait queue example exit
4 内核定时器
4.1 简介
- 标准定时器:标准定时器是内核定时器,它以Jiffy为粒度运行
- Jiffy是在
<linux/jiffies.h>
中声明的内核时间单位,其代表系统自启动以来的滴答数
HZ
表示系统在1秒钟之内的滴答数
expires = jiffies + n * HZ
,其中expires
是我们想要的滴答数
- 举个例子:假设当前系统在1秒钟之内的滴答数为100,当前总的滴答数为50,那么在2秒钟之后系统总的滴答数将会是
50 + 2 * 100 = 250
- 那么此刻聪明的同学就会问道,这个jiffy的值是否会溢出么?答案是可能会溢出。假设在一个32位系统中,jiffy的值可以从0增加到4294967295,假设1秒钟产生1000个jiffy,那么系统可以运行
4294967296 / 1000 = 4294967.296
秒,即大概49.7天左右。假如在一个64位的系统中,按照同样方法计算,系统可以运行6亿年
- 那么在32位系统里面是如何保存jiffy的值呢?在
<linux/jiffies.h>
中引入了另外一个变量,将jiffies指向低32位,jiffies_64指向高位。在64位系统中jiffies = jiffies_64
extern u64 jiffies_64;
4.2 API