Linux内核工具和辅助函数

一、理解宏container_of

 </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) ({              \
       void *__mptr = (void *)(ptr);                   \
       BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&   \
                !__same_type(*(ptr), void),            \
                "pointer type mismatch in container_of()");    \
       ((type *)(__mptr - offsetof(type, member))); })

二、链表

</include/linux/types.h>
    
   struct list_head {
       struct list_head *next, *prev;
   };
   </include/linux/list.h>
   #define LIST_HEAD_INIT(name) { &(name), &(name) } //动态初始化
   #define LIST_HEAD(name) \  //静态分配
       struct list_head name = LIST_HEAD_INIT(name)
   //添加节点
   static inline void list_add(struct list_head *new, struct list_head *head)
   {
       __list_add(new, head, head->next);
   }
   //添加到尾部
   static inline void list_add_tail(struct list_head *new, struct list_head *head)
   //删除节点
   static inline void list_del(struct list_head *entry)
   //链表遍历
   #define list_for_each_entry(pos, head, member)              \
       for (pos = list_first_entry(head, typeof(*pos), member);    \
            &pos->member != (head);                    \
            pos = list_next_entry(pos, member))

三、内核的睡眠机制

</include/linux/wait.h>
    
   struct wait_queue_head {
       spinlock_t      lock;
       struct list_head    head;
   };
   typedef struct wait_queue_head wait_queue_head_t;
   //静态声明
   #define DECLARE_WAIT_QUEUE_HEAD(name) \
       struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
   //动态声明
   #define init_waitqueue_head(wq_head)                        \
       do {                                    \
           static struct lock_class_key __key;             \
                                           \
           __init_waitqueue_head((wq_head), #wq_head, &__key);     \
       } while (0)
   //如果条件为false,则阻塞等待队列中的当前任务(进程)
   int wait_event_interruptible(wq_head, condition);
   int wait_event(wq_head, condition)
   //解除阻塞
   #define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
   #define wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)

wait_event_interruptible 不会持续轮询,而只是在被调用时评估条件。如果条件为假,则进程将进入 TASK_INTERRUPTIBLE状态并从运行队列中删除。之后当每次在等待队列中调用 wake_up_interruptible 时,都会重新检查条件。如果wake_up_interruptible 运行时发现条件为真,则等待队列中的进程将被唤醒,并将其状态设置为 TASK_RUNNING。进程按照它们进入睡眠的顺序唤醒。要唤醒在队列中等待的所有进程,应该使用wake_up_interruptible_all。
如果调用了 wake_up或wake_up_interruptible,并且条件仍然是FALSE,则什么都不会发生。如果没有调用wake_up(或wake_up_interuptible),进程将永远不会被唤醒。

   #include <linux/module.h>
   #include <linux/init.h>
   #include <linux/sched.h>
   #include <linux/time.h>
   #include <linux/delay.h>
   #include<linux/workqueue.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)
   { 
       pr_info("Waitqueue module handler %s\n", __FUNCTION__);
       msleep(3000);
       pr_info("Wake up the sleeping module\n");
       condition = 1;
       wake_up_interruptible(&my_wq);
   }
   static int __init my_init(void)
   {
       pr_info("Wait queue example\n");
       INIT_WORK(&wrk, work_handler);
       schedule_work(&wrk);
       pr_info("Going to sleep %s\n", __FUNCTION__);
       wait_event_interruptible(my_wq, condition != 0);
       pr_info("woken up by the work job\n");
       return 0;
   }
   void my_exit(void)
   {
       pr_info("waitqueue example cleanup\n");
   }
   module_init(my_init);
   module_exit(my_exit);
   MODULE_LICENSE("GPL");

四、延迟和定时器管理
内核定时器分为两个不同的部分
标准定时器或系统定时器。
高精度定时器
标准定时器:
标准定时器时内核定时器,它以jiffy为粒度运行
32位系统上采用这种方式时,jiffies将指向低32位,jiffies_64将指向高位,在64位平台上,jiffies = jiffies_64
定时器API

 </include/linux/timer.h>
   struct timer_list {
       struct hlist_node   entry; //定时器列表
       unsigned long       expires;//以jiffies为单位绝对值,定时器到期时间
       void            (*function)(struct timer_list *);//定时器处理函数
       u32         flags;
   };
   timer_setup(timer, callback, flags) //初始化定时器,主要是初始化 

timer_list 结构体,设置其中的函数、flags。
DEFINE_TIMER(_name, _function) //初始化定时器,主要是定义并初始化 timer_list 结构体,设置其中的函数,flags默认为0.
//向内核添加定时器.timer->expires 表示超时时间.当超时时间到达,内核就会调用这个函数:timer->function(timer->data).

   void add_timer(struct timer_list *timer)
   int mod_timer(struct timer_list *timer, unsigned long expires)//修改定时器的超时时间
   int timer_pending(const struct timer_list * timer)//定时器状态查询,如果在系统的定时器列表中则返回1,否则返回0;
   int del_timer(struct timer_list * timer)//删除定时器。

高精度定时器(HRT)
内核配置

CONFIG_HIGH_RES_TIMERS=y

API

</include/linux/hrtimer.h>
   struct hrtimer {
       struct timerqueue_node      node;
       ktime_t             _softexpires;
       enum hrtimer_restart        (*function)(struct hrtimer *);
       struct hrtimer_clock_base   *base;
       u8              state;
       u8              is_rel;
       u8              is_soft;
   };
   static inline ktime_t ktime_set(const s64 secs, const unsigned long nsecs)
   //初始化hrtimer
   hrtimer_init(struct hrtimer *timer, clockid_t which_clock,
                enum hrtimer_mode mode);
   //启动hrtimer
   static inline void hrtimer_start(struct hrtimer *timer, ktime_t tim,
                    const enum hrtimer_mode mode)
   //取消
   int hrtimer_cancel(struct hrtimer *timer);
   int hrtimer_try_to_cancel(struct hrtimer *timer);//如果定时器处于激活或其回调函数正在运行,则返回失败-1
   //为了防止定时器自动重启,hrtimer回调函数必须返回

HRTIMER_NORESTART
执行以下操作可以检查系统上是否可用HRT:
查看 cat /proc/timer_list 或 cat /proc/timer_list | grep resolution的结。.resolution 项必须显示1 nsecs。事件处理程序必须显示hrtimer_interrupts。

内核中的延迟和睡眠
延迟由两种类型,取决于代码运行的上下文:原子的或非原子的。
原子上下文
一般来讲原子上下文指的是在中断或软中断中,以及在持有自旋锁的时候。

</include/linux/delay.h>
    /*
     *应该始终使用udelay(),因为ndelay的精度取决于硬件定时器的精度(嵌入式SOC不一定如此)
     *不建议使用mdelay()。
     */
    void ndelay(unsigned long x)
    void udelay(unsigned long x)
    void mdelay(unsigned long x)

定时器处理程序(回调)在原子上下文中执行,这意味着根本不允许进入睡眠。这指的是可能导致调用程序进入睡眠的所有功能,如分配内存、锁定互斥锁、显式调用sleep()函数等。
非原子上下文

   udelay(unsigned long usecs)//基于繁忙-等待循环。如果需要睡眠微妙(小于等于10us),使用此函数
   //依赖于hrtimer,睡眠数微妙到毫秒(10us~20ms)时建议使用,避免使用udelay的繁忙-等待循环
   void usleep_range(unsigned long min, unsigned long max);
   void msleep(unsigned int msecs)//10ms以上使用

内核的锁机制
互斥锁
竞争者从调度器的运行队列中删除,放入处于睡眠状态的等待链表(wait_list)中。然后内核调度并执行其他任务。当锁被释放时,等待队列中的等待者被唤醒,从wait_list 移出,然后重新被调度。

</include/linux/mutex.h>
    
   //1.声明
   DEFINE_MUTEX(mutexname) //静态声明
    //动态声明
   struct mutex my_mutex
   mutex_init(&my_mytex)
   //锁定
   int  mutex_lock_interruptible(struct mutex *lock)
   void mutex_lock(struct mutex *lock)
   int mutex_lock_killable(struct mutex *lock)
   //解锁
   void  mutex_unlock(struct mutex *lock)
   //检查是否锁定
   static  bool mutex_is_locked(struct mutex *lock)

互斥锁规则:
一次只能有一个任务持有互斥锁。
不允许多次解锁。
必须通过API初始化
持有互斥锁的任务可能不会退出,因为互斥锁将保持锁定,可能的竞争者会永远等待(将睡眠)
不能释放锁所在的内存区
持有的互斥锁不得重新初始化。
由于它们涉及重新调度,因此互斥锁不能用在原子上下文中如Tasklet和定时器
自旋锁

  </include/linux/spinlock_types.h>
    
   typedef struct spinlock {
       union {
           struct raw_spinlock rlock;
       };
   } spinlock_t;
   //定义并初始化
   spinlock_t my_spinlock;
   spin_lock_init(my_spinlock);
   static void spin_lock(spinlock_t *lock) //上锁
   static void spin_unlock(spinlock_t *lock) //解锁
   static  void spin_lock_irq(spinlock_t *lock) //上锁并禁止中断
   static  void spin_unlock_irq(spinlock_t *lock) //解锁并开启中断
   static  void spin_lock_irqsave(spinlock_t *lock, unsigned long flags) //上锁并保存中断状态
   static  void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)//解锁并恢复中断状态

互斥锁与自旋锁的比较
互斥锁保护进程的关键资源,而自旋锁保护IRQ处理程序的关键部分
互斥锁让竞争者在获得锁之前睡眠,而自旋锁在获得锁之前一直自旋循环(消耗CPU)。
鉴于上一点,自旋锁不能长时间持有,因为等待者在等待取锁期间会浪费 CPU时间;而互斥锁则可以长时间持有,只要保护资源需要,因为竞争者被放入等待队列中进入睡眠状态
工作延迟机制
延迟是将所要做的工作安排在将来执行的一种方法,这种方法推后发布操作。显然内核提供了一些功能来实现这种机制;它允许延迟调用和执行任何类型函数。下面是内核中的3项功能。
SoftIRQ:执行在原子上下文
Tasklet::执行在原子上下文。
工作队列:执行在进程上下文
Softirq与Ksoftirqd
Softirq(软中断或软件中断)这种延迟机制仅用于快速处理,因为它在禁用的调度器(在中断上下文中)下运行。很少(几乎从不)直接使用 Softirq,只有网络和块设备子系统使用 Softirq。Tasklet 是 Sotirq 的实例几乎每种需要使用 Softirq 的情况有 Tasklet就足够了。
Tasklet

 </include/linux/interrupt.h>
    
   //把 count初始化为0表示 tasklet 处于激活状态
   #define DECLARE_TASKLET(name, func, data) \
   struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
   //count 初始化为1,表示该tasklet 处于关闭状态
   #define DECLARE_TASKLET_DISABLED(name, func, data) \
   struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
   //动态声明
   void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
   //启用和禁用Tasklet
   static inline void tasklet_enable(struct tasklet_struct *t)
   static inline void tasklet_disable(struct tasklet_struct *t)
   //调度 高优先级 Tasklet 旨在用于具有低延迟要求的软中断处理程序。
   static inline void tasklet_schedule(struct tasklet_struct *t)//普通优先级
   void __tasklet_hi_schedule(struct tasklet_struct *t);//高优先级
   //停止tasklet
   void tasklet_kill(struct tasklet_struct *t);

工作队列
内核有两种方法可以处理工作队列。
一种方法是默认的共享工作队列-system_wq,由一组内核线程处理,每个内核线程运行在一个 CPU上。一旦有工作任需要调度,就使该工作到全局工作队列中排队,它将在适当的时候执行。
由于队列上挂起的任务在每个 CPU 上是串行执行的,因此任务不应该长时间睡眠因为在它唤醒之前,该队列上的其他任务都无法运行,一个任务甚至不知道它和哪些任务共享工作队列,所以任务可能需要较长时间才能得到CPU。共享工作队列中的工作由每个CPU上内核创建的events/n线程执行。
在这种情况下,工作也必须用 INIT_WORK 宏来初始。由于接下来要使用共享工作队列,因此不需要创建工作队列结构。只需要使用 work_struct 结构,并把它作为参数传递。

  </include/linux/workqueue.h>
    
   //1 声明
   #define INIT_WORK(_work, _func)                     \
       __INIT_WORK((_work), (_func), 0)
   //2 调度
   static inline bool schedule_work(struct work_struct *work)
   static inline bool schedule_work_on(int cpu, struct work_struct *work)//指定CPU
   static inline bool schedule_delayed_work_on(int cpu, struct delayed_work *dwork,
                           unsigned long delay)//指定CPU和延时
   static inline bool schedule_delayed_work(struct delayed_work *dwork,
                        unsigned long delay)//指定延时,延时时jiffies
   //取消
   bool cancel_work_sync(struct work_struct *work);
   //刷新共享队列
   static inline void flush_scheduled_work(void)

另一种方法是在专用内核线程内运行工作队列。这意味着无论何时需要执行工作队列处理程序,都会唤醒专用的内核线程来处理它,而不是默认的预定义线程之一。
在内核线程中调度工作之前需要执行以下4步:
使用alloc_workqueue()函数创建新的工作队列。
使用INIT_WORK()宏声明一个 work 及其回调函数
通过queue_work()在新工作队列上调度一个 work。
通过flush_workqueue()刷新工作队列上所有的 work。

   /* 1. 自己创建一个workqueue, 中间参数为0,默认配置 */
   workqueue_test = alloc_workqueue("workqueue_test", 0, 0);
   /* 2. 初始化一个工作项,并添加自己实现的函数 */
   INIT_WORK(&work_test, work_test_func);
    /* 3. 将自己的工作项添加到指定的工作队列去, 同时唤醒相应线程处理 */ 
   queue_work(workqueue_test, &work_test);
内核中断机制
   </include/linux/interrupt.h>
   /*
    *name:内核用来标识/proc/ubterrupts和/proc/irq中的驱动程序
    *dev:作为参数传递给中断处理程序
    *handler:中断发生时运行的回调函数。
    */
   int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
           const char *name, void *dev)
   //handler 中断处理程序的结构如下:
   static irqreturn_t (*irq_handler_t)(int irq, void *dev)
   {
       return IRQ_HANDLED;//设备引发中断
       return IRQ_NONE;  //设备不是中断的发起者
   }
   //释放已注册的中断处理函数
   void free_irq(unsigned int irq,void *dev);
    
    
   </include/linux/irqreturn.h>
   enum irqreturn {
       IRQ_NONE        = (0 << 0),
       IRQ_HANDLED     = (1 << 0),
       IRQ_WAKE_THREAD     = (1 << 1),
   };
   typedef enum irqreturn irqreturn_t;
   #define IRQ_RETVAL(x)   ((x) ? IRQ_HANDLED : IRQ_NONE)

两个不同的中断处理程序间共享数据时(也就是同一个驱动程序管理两个或多个设备;每一个设备都有其自己的中断线),在这些处理程序中还应该使用:spin_lock_irqsave()来保护共享数据,以防止其他IRQ触发和无用的自旋等待。
下半部的概念
无论中断处理程序是否持有自旋锁,在运行该中断处理程序的CPU上都会禁止占。中断处理程序浪费的时间越多,给予其他任务的CPU 时间就越少。
在 Linux 系统上(实际上在所有操作系统上,硬件设计决定),任何中断处理序运行时,都会在所有处理器上禁用其当前中断线,有时可能需要在实际运行处理程序的CPU上禁止所有中断,但绝对不会希望错过中断。
下半部:
第一部分称作上半部或者硬IRQ,它使用 request_irq()注册函数,最终将根据需要屏蔽/隐藏中断,执行快速操作(实际上是时间敏感务,读写硬件寄存器,以及快速处理此数据),调度第二部分和下一部分,然后确认中断线禁用的所有中断都必须在退出下半部之前重新启用。
第二部分称作下半部,会处理一些比较耗时的任务,在它执行期间,中断再次启用。这样就不会错过中断。下半部设计使用了工作延迟机制。
线程化中断
线程化中断(threaded IRQ)的主要目标是将中断用的时间减少到最低限度。使用线程化中断,注册中断处理程序的方式将得到简化。甚至不必自己调度下半部。核心部分会完成。下半部在专用内核线程中执行。使用

request_threaded_irq()来代替request_irq()。
</include/linux/interrupt.h>
   /*
    *@handler:这与使用request_irq()注册时使用的函数一样。它表示上半部函数,
    *   在原子上下文(或硬中断)中运行。如果它能更快地处理中断,就可以根本不用下
    *   半部,它应该返回IRQ_HANDLED。但是,如果中断处理需要100us以上,则应该使
    *   用下半部。在这种情况下,它应该返回IRQ_WAKE_THREAD,从而导致调度thread_fn 
    *   函数(必须提供)。
    *@thread_fn:这代表下半部,由上半部调度。当硬中断处理程序(handler函数)返回
    *   IRQ_WAKE_THREAD时,将调度与该下半部相关联的内核线程,在内核线程运行时调用 
    *   thread_fn 函数。thread_fn函数完成时必须返回IRQ_HANDLED。执行后,再重新触
    *   发该中断,并且在硬中断返回IRQ_WAKE_THREAD之前,内核线程不会被再次调度。 
    */
   int request_threaded_irq(unsigned int irq, irq_handler_t handler,
                irq_handler_t thread_fn,
                unsigned long flags, const char *name, void *dev);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值