Linux内核工具和常用辅助函数

一、内核中的链表操作

内核中常用的链表为双向链表,其数据结构为struct list_head。

struct list_head {
   struct list_head *next, *prev;
};

 一般的用法是把struct list_head嵌入到数据结构中,例如:

struct car {
    struct list_head list;
    int door_number;
    char *color;
    char *model;
};

静态和动态方式初始化链表list_head

/*对于struct car,静态初始化方法:*/
LIST_HEAD(car_list);

/*动态初始化 car List*/
/*struct list_head car_list;
INIT_LIST_HEAD(&car_list);*/

向list_head中添加节点或删除节点

struct car *white_car = kzalloc(sizeof(struct car), GFP_KERNEL);
struct car *black_car = kzalloc(sizeof(struct car), GFP_KERNEL);
list_add(&black_car->list, &car_list); //加到表头
list_add_tail(&white_car->list, &car_list); // 加到表尾

list_del(&black_car->list); // 删除链接节点

遍历链表节点:

struct car *c;
int black_car_num = 0;
list_for_each_entry(c, car_list, list) {  //正常访问,不会添加和删除
   if (c->color == "black") black_car_num++;
}

//如果需要添加和删除,则使用list_for_each_entry_safe

二、内核中的延迟和睡眠

1 延迟和睡眠

// 在原子上下文(ISR中,不能进入睡眠状态)中,会忙等待,一般会使用udelay
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs); // 一般不会使用

// 在非原子上下文中,会进入睡眠和切换到其他线程
void msleep(unsigned int millisecs);
int msleep_interruptible(unsigned int millisecs);  // 返回设定请求时间的剩余ms数
void ssleep(unsigned int seconds);

2 jiffies和HZ

内核定时器以jiffies为粒度运行,HZ是jiffies每秒递增的次数;

3 内核等待队列wait_queue_head_t

等待队列一般用于处理被阻塞的I/O,以等待特定条件成立,并感知数据和资源的可用性。

struct __wait_queue {
    unsigned int flags;
    void *private;
    wait_queue_func_t func;
    struct list_head task_list; // 所有等待的 任务 都挂载在该链表上;
};

wait_queu使用:

DECLARE_WAIT_QUEUE_HEAD(name); // 静态初始化

// 动态初始化
wait_queue_head_t my_wait_queue;
init_waitqueue_head(&my_wait_queue);

//阻塞
int wait_event_interruptible(wait_queue_head_t *q, CONDITION);

//触发wait_queue
void wake_up_interruptible(wait_queue_head_t *q);

其中wait_event_intertuptible不会持续轮询,只是在调用时评估条件;如果为假则任务进入TASK_INTERRUPTIBLE状态。之后每次调用wake_up_interruptible时所有的等待任务都会被唤醒并检查条件,如果条件为false则不会检查条件。如果要换新所有等待的任务,则调用wake_up_interruptible_all()。

实际上有wait_event/wait_event_interruptible两个版本,其中wait_event只能被wake_up唤醒,无法被信号唤醒(Ctrl+c)。因此尽量使用xxx_interruptible版本;

4. 定时器Timer

使用struct timer_list进行控制,查看/proc/jittimer可检查当前注册的timer。

struct timer_list {
    struct list_head entry;
    unsigned long expires;  // 以jiffies为单位绝对值
    struct tvec_t_base_s *base;
    void (*function)(unsigned long);
    unsigned long data;
};

定时器操作函数

// 设置定时器
void setup_timer(struct timer_list *timer, void (*function)(unsigned long), void *data);

// 设定过期时间
int mod_timer(struct timer_list *timer, unsigned long expires);

// 释放定时器
void del_timer(struct timer_list *timer); // 不等待正在执行的定时器执行完成
int del_timer_sync(struct timer_list *timer); // 等待正在执行的timer执行完成后,再退出

// 检查定时器释是否正在运行
int timer_pending(struct timer_list *timer);

timer使用示例:

static struct timer_list my_timer;

void my_timer_cb(unsigned long data) {
    printk("func %s (%ld)", __FUNCTION__, jiffies);
}

static int __init my_init(void) {
    setup_timer(&my_timer, my_timer_cb, 0);
    ret = mod_timer(&my_timer, jiffies + msecs_to_jiffies(100));
    if (ret < 0) {
        printk("setup 100ms timer failed.");
    }
    return 0;
}

static void __exit my_exit(void) {
    del_timer(&my_timer);
}

module_init(my_init);
module_exit(my_exit);

5. 内核的HRTimer

是否启动高精度定时器对应的内核选项为CONFIG_HIGH_RES_TIMER,高精度定时器使用struct hrtimer{}

//初始化
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode);
//启动定时器
int hrtimer_start(struct hrtimer *timer, ktime_t time, const enum hrtimer_mode mode);
// 取消定时器
int hrtimer_cancel(struct hrtimer *timer);
int hrtimer_try_to_cancel(struct hrtimer *timer);
// 检查定时器是否正在执行


// 为防止定时器自动重启,hrtimer的回调函数必须返回HRTIMER_NORESTART

三、内核锁spinlock_t和semphare_t

1 自旋锁spinlock_t

注意:处理自旋锁时,只有持有自旋锁的任务的抢占被禁止,而自旋锁的等待者并没有禁止抢占;

相关操作

//初始化
spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
void spin_lock_init(spinlock_t *lock);
//锁住
void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);

// 非阻塞等待锁
int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);

//释放锁
void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);

2 读者写者自旋锁

//初始化
rwlock_t my_lock = RW_LOCK_UNLOCKED;
void rwlock_init(rwlock_t *lock);

void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);

void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

void write_lock(rwlock_t *lock);
int write_trylock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);

void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

3 信号量semphore

信号量使用结构体struct semphore{};

相关操作函数:

struct semphore sem;

// 动态初始化
init_sem(&sem, 1); // 原型void init_sem(struct semphore *sem, int val);

// 动态初始化为mutex
// int_MUTEX(&sem);
// int_MUTEX_LOCKED(&sem);

//静态初始化为Mutex
/*
DECLARE_MUTEX(my_mu);
DECLARE_MUTEX_LOCKED(my_mu);
*/

// 获取信号量
down(&sem);
down_interruptible(&sem);
down_trylock(&sem); // int down_trylock(struct semphore *sem);

// 释放信号量
up(&sem);

4 读写信号量struct rw_semphore

操作函数

struct rw_semphore rw_sem;

// 静态初始化
// 动态初始化
void init_rwsem(struct rw_semphore *sem);

// 读锁相关操作
void down_read(struct rw_semphore *sem);
int down_read_trylock(struct rw_semphore *sem);
void up_read(struct rw_semphore *sem);

// 写锁相关操作
void down_write(struct rw_semphore *sem);
int down_write_trylock(struct rw_semphore *sem);
void up_write(struct rw_semphore *sem);
void downgrade_write(struct rw_semphore *sem);

5 等待完成completion

对于内核的常见模式,在任务之外初始化某个活动,然后等待该活动完成;内核专门提供了completion来实现这种模式。

// 静态初始化
// DECLARE_COMPLETION(my_cmpl);
// 动态初始化
struct completion my_cmpl;
init_completion(&my_cmpl);

// 等待活动完成
void waif_for_completion(struct completion *c);

// 通知活动完成
void complete(struct completion *c);
void complete_all(struct completion *c);

注意:completion是单次设备,如果使用了一次后还想使用该设备,则必须调用init_completion重新初始化一次该变量;

6. 原子变量atomic_t

atomic_t v = ATOMIC_INIT(0);

void atomic(atomic_t *v, int i);
int atomic_read(atomic_t *v);

// 增减
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
void atomic_sub(int i, atomic_t *v);
void atomic_add(int i, atomic_t *v);

int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_add_return(int i, atomic_t *v);

// 特定操作后进行test,如果原子值为0则返回值为true,注意不可能有atomic_add_and_test
int atomic_inc_test(atomic_t *v);
int atomic_dec_test(atomic_t *v);
int atomic_sub_test(int i, atomic_t *v);

7 信号量和自旋锁的应用场景

  • 信号量用于保护内核进程的关键资源,而自旋锁则在原子上下文中使用;
  • 互斥锁让竞争者在获得锁之前睡眠,而自旋锁在获得锁之前一直自选循环;
  • 自旋锁不能长时间持有,因为等待者会自旋等待浪费CPU资源;而semphore则可以长时间持有;

四、内核中断后半段机制

内核中断的后半段一种是基于softirq机制来实现,在softirq机制之上,实现了tasklet,它运行在中断上下文中。另一种是基于工作队列workqueue机制,它运行在进程上下文中;

1. tasklet机制

tasklet的在内核中的注册管理使用的是一个简单的链表,查看/proc/jittasklet和/proc/jittasklethi可查看当前注册的tasklet。

tasklet的初始化

struct tasklet_struct {
/* ... */
void (*func)(unsigned long data);
unsigned long data;
};

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
DECLARE_TASKLET(name, func, data);
DECLARE_TASKLET_DISABLED(name, func, data);

tasklet的管理使用

// 禁用tasklet
void tasklet_disable(struct tasklet_struct *t);
void tasklet_disable_nosync(struct tasklet_struct *t);

void tasklet_enable(struct tasklet_struct *t);

void tasklet_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule(struct tasklet_struct *t);

void tasklet_kill(struct tasklet_struct *t);

2. 工作队列机制

工作队列机制工作在进程上下文空间,在其中可以使用睡眠相关的函数。

创建工作队列和清除队列:

// 内核会在每个CPU上为该workqueue创建一个专用线程;
struct workqueue_struct * create_workqueue(const char *name); 

// 内核只在当前cpu上为该工作队列创建一个线程
struct workqueue_struct * create_singlethread_workqueue(const char *name); 



// 清除workqueue中的所有任务
int flush_workqueue(struct workqueue_struct *q);

// 销毁workqueue
int destroy_workqueue(struct workqueue_struct *q);

工作任务操作函数:

// 静态初始化一个工作任务
DECLARE_WORK(name, void(*fn)(void*), void *data);

// 动态初始化一个工作任务
INIT_WORK(struct work_struct *work, void(*fn)(void*), void *data);

// 提交工作任务
int queue_work(struct workqueue_struct *q, struct work_struct *work);

// 要求任务在给定的jiffies时间后运行
int queue_delayed_work(struct workqueue_struct *q, struct work_struct *work, unsigned long delay);
// 取消被延迟的任务
int cancel_delayed_work(struct work_struct *work);

// 已在链表中的工作任务,也可以改变其操作函数
PREPARE_WORK(struct work_struct *work, void(*fn)(void*), void *data);

有时只是偶尔需要向工作队列提交任务,并不需要专门创建一个工作任务队列,可以使用共享的工作任务队列,并且还可以在/proc/jitwq或/proc/jitwqdelay中查看正在被执行的工作任务

使用共享任务队列的方法:

int schedule_work(struct work_struct *work);

int schedule_delayed_work(struct work_struct *work, unsigned long delay);

int cancel_delayed_work(struct work_struct *work);

void flush_scheduled_work(void);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值