menu

篇一:在Linux内核驱动中常用到API接口函数

  • date_range 04/01/2022 00:00
    点击量:
    info
    sort
    Linux
    label
    linux

并发与竞争

原子操作

定义:

typedef struct {
    int counter;
} atomic_t;

整形操作:

ATOMIC_INIT(int i);//定义时初始化
int atomic_read(atomic_t *v);
void atomic_set(atomic_t *v, int i)
void atomic_add(int i, atomic_t *v)
void atomic_sub(int i, atomic_t *v)
void atomic_dec(atomic_t *v)//自减
void atomic_inc(atomic_t *v)//自增
int atomic_dec_return(atomic_t *v)//自减并返回v
int atomic_inc_return(atomic_t *v)//自增并返回v
int atomic_sub_and_test(int i, atomic_t *v)//(v-i)==0?1:0(返回真、假)
int atomic_dec_and_test(atomic_t *v)//(v--)==0?1:0(返回真、假)
int atomic_inc_and_test(atomic_t *v)//(v++)==0?1:0(返回真、假)
int atomic_add_negative(int i, atomic_t *v)//(v+i)<0?1:0(返回真、假)

位操作(直接对内存操作):

void set_bit(int nr, void *p)//将p地址的第nr位置 1
void clear_bit(int nr,void *p)
void change_bit(int nr, void *p)//翻转
int test_bit(int nr, void *p)//获取
int test_and_set_bit(int nr, void *p)//将p地址的第nr位置1,并返回nr位原来的值
int test_and_clear_bit(int nr, void *p)//...清零...
int test_and_change_bit(int nr, void *p)//...翻转...

自旋锁

基本API

DEFINE_SPINLOCK(spinlock_t lock)//定义并初始化 int spin_lock_init(spinlock_t *lock)//初始化 void spin_lock(spinlock_t *lock)//加锁 void spin_unlock(spinlock_t *lock)//解锁 int spin_trylock(spinlock_t *lock)//尝试加锁 int spin_is_locked(spinlock_t *lock)//检查是否加锁,是返回非 0,否返回 0

中断相关:

一般在线程中使用 spin_lock_irqsave/spin_unlock_irqrestore,在中断中使用spin_lock/spin_unlock

void spin_lock_irq(spinlock_t *lock)//禁止本地中断,并获取自旋锁
void spin_unlock_irq(spinlock_t *lock)//激活本地中断,并释放自旋锁
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags)//保存中断状态,禁止本地中断,并获取自旋锁
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)//恢复中断状态,并且激活本地中断,释放自旋锁

中断下半部相关:

void spin_lock_bh(spinlock_t *lock)//关闭下半部,并获取自旋锁
void spin_unlock_bh(spinlock_t *lock)//打开下半部,并释放自旋锁

信号量

DEFINE_SEAMPHORE(name)//定义信号量 并设值为1
void sema_init(struct semaphore *sem, int val)//初始化,并设值为val
void down(struct semaphore *sem)//获取信号量(会导致休眠,不能在中断中使用,不能被信号打断)
int down_interruptible(struct semaphore *sem)//获取信号量(会导致休眠,不能在中断中使用,可以被信号打断)
int down_trylock(struct semaphore *sem)//尝试获取信号量(成功返回 0。否则返回非 0)
void up(struct semaphore *sem)//释放信号量

互斥体

会导致休眠,不能在中断中使用 mutex,中断中只能使用自旋锁

DEFINE_MUTEX(name)//定义并初始化
void mutex_init(mutex *lock)//初始化
void mutex_lock(struct mutex *lock)//上锁,失败则休眠,不可以被信号打断
int mutex_lock_interruptible(struct mutex *lock)//上锁,失败则休眠,可以被信号打断
void mutex_unlock(struct mutex *lock)//解锁
int mutex_trylock(struct mutex *lock)//尝试加锁,成功返回1,失败返回0
int mutex_is_locked(struct mutex *lock)//判断是否加锁,是返回1,否返回0

字符设备驱动

常用宏

module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author");
//printk
#define KERN_EMERG KERN_SOH "0" /* 紧急事件,一般是内核崩溃 */
#define KERN_ALERT KERN_SOH "1" /* 必须立即采取行动 */
#define KERN_CRIT KERN_SOH "2" /* 临界条件,比如严重的软件或硬件错误*/
#define KERN_ERR KERN_SOH "3" /* 错误状态,一般设备驱动程序中使用KERN_ERR 报告硬件错误 */
#define KERN_WARNING KERN_SOH "4" /* 警告信息,不会对系统造成严重影响 */
#define KERN_NOTICE KERN_SOH "5" /* 有必要进行提示的一些信息 */
#define KERN_INFO KERN_SOH "6" /* 提示性的信息 */
#define KERN_DEBUG KERN_SOH "7" /* 调试信息 */

注册与注销

老方法,不推荐:

//注册字符设备(浪费设备号)
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
//注销字符设备
static inline void unregister_chrdev(unsigned int major, const char *name);
name:设备名字,指向一串字符串

新方法,推荐:

/*注册(方法一,自己确定设备号)*/
int register_chrdev_region(dev_t from, unsigned count, const char *name);//详见设备号API
cdev.owner = THIS_MODULE;
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
/*注册(方法二,系统分配设备号)*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);//详见设备号API
cdev.owner = THIS_MODULE;
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
/*注销*/
void cdev_del(struct cdev *p);
void unregister_chrdev_region(dev_t from, unsigned count);

设备节点

类相关:

class_create(owner, name)//创建类 owner一般为THIS_MODULE
void class_destroy(struct class *cls);//卸载类

设备相关:

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);//创建设备 parent=NULL drvdata=NULL
void device_destroy(struct class *class, dev_t devt)//卸载设备

设备号

相关宏:

MAJOR(dev)//dev_t -> 主设备号
MINOR(dev)//dev_t -> 次设备号
MKDEV(ma,mi)//主设备号+次设备号 -> dev_t

动态分配设备号:

系统分配设备号+自动注册设备号

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
dev:存放起始设备编号的指针,当注册成功,*dev就会等于分配到的起始设备编号,可以通过MAJOR()和MINNOR()宏来提取主次设备号
baseminor:次设备号基地址,也就是起始次设备号
count:要申请的数量,一般都是一个
name:字符设备名称
返回值小于0,表示注册失败

注册设备号:

手动确定设备号+手动注册设备号

int register_chrdev_region(dev_t from, unsigned count, const char *name)
from:要申请的起始设备号,也就是给定的设备号
count:要申请的数量,一般都是一个
name:设备名字
当返回值小于0,表示注册失败

用户空间与内核空间

内核空间–>用户空间

static inline long copy_to_user(void __user *to, const void *from, unsigned long n)
to:用户空间指针
from:内核空间指针
n:从内核空间向用户空间拷贝数据的字节数
成功返回0,失败返回失败数目

用户空间–>内核空间

static inline long copy_from_user(void *to, const void __user * from, unsigned long n)
to:内核空间指针
from:用户空间指针
n:从用户空间向内核空间拷贝数据的字节数
成功返回0,失败返回失败数目

地址映射

#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
phys_addr:要映射的物理起始地址。
size:要映射的内存空间大小。
void iounmap (volatile void __iomem *addr)

对映射后的内存进行读写操作的函数:

//读

u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)

//写

void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)

Linux常用命令

#查看设备号
cat /proc/devices
#创建设备节点
mknod /dev/xxx c 主设备号 次设备号
#查看设备树节点
ls /proc/device-tree
ls /sys/firmware/devicetree/base
#查看platform相关
ls /sys/bus/platform/devices # 设备
ls /sys/bus/platform/drivers # 驱动
#查看misc相关
ls /sys/class/misc # 驱动
#驱动相关
depmod
modprobe xxx.ko
insmod xxx.ko
lsmod
rmmod xxx.ko

内核定时器

extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

相关变量:

HZ:宏,就是CONFIG_HZ,系统频率,通过menuconfig配置,默认100 jiffies、jiffies_64:系统运行的节拍数,由宏HZ确定,默认10ms累加一次 jiffies/HZ:系统运行时间(s) 时间先后函数:

//unkown 通常为 jiffies,known 通常是需要对比的值

time_after(unkown, known)//unkown 时间上超过 known 则返回真
time_before(unkown, known)//unkown 时间上滞后 known 则返回真
time_after_eq(unkown, known)//unkown 时间上超过或等于 known 则返回真
time_before_eq(unkown, known)//unkown 时间上滞后或等于 known 则返回真

时间转换函数:

int jiffies_to_msecs(const unsigned long j)//jiffies类型j --> ms
int jiffies_to_usecs(const unsigned long j)//jiffies类型j --> us
u64 jiffies_to_nsecs(const unsigned long j)//jiffies类型j --> ns
long msecs_to_jiffies(const unsigned int m)//ms --> jiffies
long usecs_to_jiffies(const unsigned int u)//us --> jiffies
unsigned long nsecs_to_jiffies(u64 n)//ns --> jiffies

短延时函数:

void ndelay(unsigned long nsecs)
void udelay(unsigned long usecs)
void mdelay(unsigned long mseces)

定时器类型:

struct timer_list {
    struct list_head entry;
    unsigned long expires; /* 定时器超时时间,单位是节拍数 */
    struct tvec_base *base;
    void (*function)(unsigned long); /* 定时处理函数 */
    unsigned long data; /* 要传递给 function 函数的参数 */
    int slack;
};

定时器函数:

void init_timer(struct timer_list *timer);//初始化
void add_timer(struct timer_list *timer);//注册并使能
int del_timer(struct timer_list * timer);//删除(需等待 定时处理函数 退出)
int del_timer_sync(struct timer_list *timer);//删除(需等待其他处理器使用完定时器,中断上下文勿用)
int mod_timer(struct timer_list *timer, unsigned long expires);//修改定时值(会使能定时器)
返回值: 0,定时器未被使能; 1,定时器已经使能

中断

上半部

申请

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,可以在文件include/linux/interrupt.h里面查看所有的中断标志。

常用的中断标志:

标志 描述

IRQF_SHARED	多个设备共享一个中断线,共享的所有中断都必须指定此标志。 如果使用共享中断的话,request_irq函数的 dev 参数是唯一区分他们的标志
IRQF_ONESHOT	单次中断,中断执行一次就结束
IRQF_TRIGGER_NONE	无触发
IRQF_TRIGGER_RISING	上升沿触发
IRQF_TRIGGER_FALLING	下降沿触发
IRQF_TRIGGER_HIGH	高电平触发
IRQF_TRIGGER_LOW	低电平触发
name:中断名字,设置以后可以在/proc/interrupts文件中看到对应的中断名字
dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数
返回值: 0 中断申请成功;其他负值:中断申请失败;-EBUSY:中断已经被申请了

对于一种不太重要而又比较耗时的中断,可以使用以下函数进行申请:

int devm_request_threaded_irq(struct device *dev, unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id)

该函数会使得中断线程化(中断使用下半部虽然可以被延迟处理,但是依旧先于线程执行,中断线程化可以让这些比较耗时的下半部与进程进行公平竞争) 带devm_前缀表明申请到的资源可以由系统自动释放,无需手动处理 注意:并不是所有的中断都可以被线程化。该函数有利有弊,具体是否使用需要根据实际情况来衡量(触摸中断一般会用) 释放

void free_irq(unsigned int irq, void *dev)
irq: 要释放的中断
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉

中断处理函数 格式:

irqreturn_t (*irq_handler_t) (int, void *)

返回值:

一般为return IRQ_RETVAL(IRQ_HANDLED)

enum irqreturn {
    IRQ_NONE = (0 << 0),
    IRQ_HANDLED = (1 << 0),
    IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;

使能与禁止

void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)//需等待 正在执行的中断处理函数执行完
void disable_irq_nosync(unsigned int irq)//无需等待 正在执行的中断处理函数执行完

//不推荐

local_irq_disable()//关闭全局中断
local_irq_enable()//使能全局中断

//推荐

local_irq_save(flags)//保存中断标志到flags并 关闭全局中断
local_irq_restore(flags)//根据flags设置中断标志并 使能全局中断

下半部 推荐使用tasklet实现

软中断 定义:

struct softirq_action
{
    void (*action)(struct softirq_action *);
};

系统定义的10个软中断:

多核处理器中,各个 CPU 都有自己的触发和控制机制,并且只执行自己所触发的软中断,但是所执行的软中断服务函数却是相同的:

enum
{
    HI_SOFTIRQ=0,//高优先级软中断
    TIMER_SOFTIRQ,//定时器软中断
    NET_TX_SOFTIRQ,//网络数据发送软中断
    NET_RX_SOFTIRQ,//网络数据接收软中断
    BLOCK_SOFTIRQ,
    BLOCK_IOPOLL_SOFTIRQ,
    TASKLET_SOFTIRQ,//tasklet 软中断
    SCHED_SOFTIRQ,//调度软中断
    HRTIMER_SOFTIRQ,//高精度定时器软中断
    RCU_SOFTIRQ,//RCU 软中断
    NR_SOFTIRQS
};

static struct softirq_action softirq_vec[NR_SOFTIRQS];

注册软中断:

软中断必须在编译的时候静态注册。Linux内核默认会在软中断初始化函数softirq_init中打开TASKLET_SOFTIRQ和HI_SOFTIRQ软中断

void open_softirq(int nr, void (*action)(struct softirq_action *))
nr:要开启的软中断,在上述enum中选择一个
action:软中断对应的处理函数

触发软中断:

void raise_softirq(unsigned int nr)

tasklet 定义:

struct tasklet_struct
{
    struct tasklet_struct *next; /* 下一个 tasklet */
    unsigned long state; /* tasklet 状态 */
    atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
    void (*func)(unsigned long); /* tasklet 执行的函数,用户定义,相当于中断处理函数 */
    unsigned long data; /* 函数 func 的参数 */
};

初始化:

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);//初始化
DECLARE_TASKLET(name, func, data)//宏,定义并初始化
func:tasklet 的处理函数
data:要传递给 func 函数的参数

调度函数:

一般在中断的上半部中调用

void tasklet_schedule(struct tasklet_struct *t)
t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name

工作队列 工作队列在进程上下文执行,即,将要推后的工作交给一个内核线程去执行,因此工作队列允许睡眠或重新调度。

如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet

定义:

工作–>工作队列–>工作者线程

//工作(重点关注):

struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func; /* 工作队列处理函数 */
};

//工作队列:

struct workqueue_struct {
    struct list_head pwqs;
    struct list_head list;
    struct mutex mutex;
    int work_color;
    int flush_color;
    atomic_t nr_pwqs_to_flush;
    struct wq_flusher *first_flusher;
    struct list_head flusher_queue;
    struct list_head flusher_overflow;
    struct list_head maydays;
    struct worker *rescuer;
    int nr_drainers;
    int saved_max_active;
    struct workqueue_attrs *unbound_attrs;
    struct pool_workqueue *dfl_pwq;
    char name[WQ_NAME_LEN];
    struct rcu_head rcu;
    unsigned int flags ____cacheline_aligned;
    struct pool_workqueue __percpu *cpu_pwqs;
    struct pool_workqueue __rcu *numa_pwq_tbl[];
};

//工作者线程

struct worker {
    union {
        struct list_head entry;
        struct hlist_node hentry;
    };
    struct work_struct *current_work;
    work_func_t current_func;
    struct pool_workqueue *current_pwq;
    bool desc_valid;
    struct list_head scheduled;
    struct task_struct *task;
    struct worker_pool *pool;
    struct list_head node;
    unsigned long last_active;
    unsigned int flags;
    int id;
    char desc[WORKER_DESC_LEN];
    struct workqueue_struct *rescue_wq;
};

初始化:

#define INIT_WORK(_work, _func)//初始化,需要自己创建work_struct
#define DECLARE_WORK(n, f)//创建和初始化,无需自己创建work_struct

调度:

一般在中断的上半部中调用

bool schedule_work(struct work_struct *work)
work: 要调度的工作
返回值: 0 成功,其他值 失败

篇幅较长,拆分成多篇


更多文章 请访问havte官网

havte官网

关注微博 微信公众号

随时随地 想看就看

微信搜索公众号: havte
wechat 微信公众号:havte
微博搜索: havte官方微博
微博账号:havte官方微博