linux 驱动开发常用知识点与API

news/2025/1/12 15:43:29/

linux 驱动开发常用知识点与API

    • 前言
    • 笔记正文
    • 最后

前言

之前的读书笔记,以.c 文件的方式记录,在这里也以代码的方式记录

笔记正文

/***************************************中断常用API****************************************/
/*
flags 是中断处理的属性,若设置了 SA_INTERRUPT,则表示中断处理程序
是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若
设置了 SA_SHIRQ,则表示多个设备共享中断,dev_id 在中断共享时会用到,一般
设置为这个设备的设备结构体或者 NULLrequest_irq()返回 0 表示成功,返回-INVAL 表示中断号无效或处理函数指针为
NULL,返回-EBUSY 表示中断已经被占用且不能共享
*/static inline int __must_check
request_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);
}const void *free_irq(unsigned int irq, void *dev_id);//下面对系统所有cpu生效
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq); //等待目前的中断处理完成
void enable_irq(unsigned int irq);// 以local_开头的方法的作用范围是本 CPU 内
//屏蔽本 CPU 内的所有中断
void local_irq_save(unsigned long flags); //将目前的中断状态保留在 flags 中
void local_irq_disable(void);
//与之对应的恢复中断
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);/*********************tasklet 的使用*****************/
void my_tasklet_func(unsigned long); /*定义一个处理函数*/
/*定义一个 tasklet 结构 my_tasklet,与 my_tasklet_func(data)函数相关联*/
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data); //data 是前面long的参数tasklet_schedule(&my_tasklet); //调度 tasklet ,在中顶半部(中断注函数可调用)/*********************工作队列的使用*****************/
struct work_struct my_wq; /*定义一个工作队列*/
void my_wq_func(unsigned long); /*定义一个处理函数*//*初始化工作队列并将其与处理函数绑定*/
INIT_WORK(&my_wq, (void (*)(void *)) my_wq_func, NULL);/*调度工作队列执行,放在中断服务程序中调用*/
schedule_work(&my_wq);/*********************定时器*****************/
struct timer_list {/** All fields that change during normal runtime grouped to the* same cacheline*/struct hlist_node	entry; //定时器列表unsigned long		expires; //定时器到期时间void			(*function)(struct timer_list *); //定时器处理函数u32			flags; //作为参数被传入定时器处理函数#ifdef CONFIG_LOCKDEPstruct lockdep_map	lockdep_map;
#endif
};struct timer_list my_timer; //定义一个定时器
timer_setup(timer, callback, flags); //4 点几版的本内核可以用这个宏来初始化内核
DEFINE_TIMER(name,callback_func);//也可以使用这个来定义一个定时器void add_timer(struct timer_list *timer); 
int mod_timer(struct timer_list *timer, unsigned long expires); //修改定时器
int del_timer(struct timer_list *timer); //删除定时器//相互转换
msecs_to_jiffies;
usecs_to_jiffies;/*********************内核延时*****************/
//下面三个延迟的实现原理本质上是忙等待,它根据 CPU 频率进行一定次数的循环
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);//上述ms 级延时不推荐使用,对CPU影响很大,可以用下面ms级的
//下面函数将使得调用它的进程睡眠参数指定的时间,msleep()、ssleep()不能被打断,而 msleep_interruptible()则可以被打断
void msleep(unsigned int millisecs);/*如果你的驱动位于一个等待队列并且你想唤醒来打断睡眠, 
使用 msleep_interruptible. 从 msleep_interruptible 的返回值正常地是 0; 如果这个进程被提早唤醒,
返回值是在初始请求睡眠周期中剩余的毫秒数. 对 ssleep 的调用使进程进入一个不可中断的睡眠给定的秒数.
*/
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);#define time_before(a,b)	time_after(b,a)
time_after(a,b);
/*延迟 100 个 jiffies*/
unsigned long delay = jiffies + 100;
while (time_before(jiffies, delay)); // jiffies < delay 为真/*
fatal_signal_pending/signal_pending()接口为内核提供用于捕捉信号接口,
一般在开发驱动中为了防止死循环卡死内核,可以使用上述接口用于捕获KILL信号
*/
signal_pending();//msleep_interruptible 和ssleep 都依赖于schedule_timeout
signed long __sched schedule_timeout(signed long timeout); /*********************内存分配*****************/
/*
其他的相对不常用的申请标志还包括 
GFP_USER(用来为用户空间页分配内存,可能阻塞)、
GFP_HIGHUSER(类似 GFP_USER,但是从高端内存分配)、
GFP_NOIO(不允许任何 I/O 初始化)、
GFP_NOFS(不允许进行任何文件系统调用)、
_ _GFP_DMA(要求分配在能够 DMA 的内存区)、
_ _GFP_HIGHMEM(指示分配的内存可以位于高端内存)、
_ _GFP_COLD(请求一个较长时间不访问的页)、
_ _GFP_NOWARN(当一个分配无法满足时,阻止内核发出警告)、
_ _GFP_HIGH(高优先级请求,允许获得被内核保留给紧急状况使用的最后的内存页)、
_ _GFP_REPEAT(分配失败则尽力重复尝试)、_
_GFP_NOFAIL(标志只许申请成功,不推荐)和
_ _GFP_NORETRY(若申请不到,则立即放弃
*///GFP 缩写为 get free pages
/*使用 GFP_ KERNEL 标志申请内存时,若暂时不能满足,则进程会睡眠等待页,即会引起阻塞,
因此不能在中断上下文或持有自旋锁的时候使用GFP_KERNE 申请内存 
*///vmalloc 用于分配大量的内存,分配开销大,可能会发生睡眠。//slab与内存池
//用于创建一个 slab 缓存
struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t align,slab_flags_t flags, void (*ctor)(void *));
void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags);
void kmem_cache_free(struct kmem_cache *s, void *x); //释放 slab 缓存   先free 再 destroy
void kmem_cache_destroy(struct kmem_cache *s); //收回 slab 缓存//下面是slab 的使用示例
/*创建 slab 缓存*/
static kmem_cache_t *xxx_cachep;
xxx_cachep = kmem_cache_create("xxx", sizeof(struct xxx),0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);/*分配 slab 缓存*/
struct xxx *ctx;
ctx = kmem_cache_alloc(xxx_cachep, GFP_KERNEL);/*释放 slab 缓存*/
kmem_cache_free(struct kmem_cache * s, void * x)(xxx_cachep, ctx);
kmem_cache_destroy(xxx_cachep);//内存池的使用. mempool_create()函数用于创建一个内存池,min_nr 参数是需要预分配对象的数
//目,alloc_fn 和 free_fn 是指向内存池机制提供的标准对象分配和回收函数的指针
mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,mempool_free_t *free_fn, void *pool_data);
// /linux/mempool.h
typedef void * (mempool_alloc_t)(gfp_t gfp_mask, void *pool_data);
typedef void (mempool_free_t)(void *element, void *pool_data);void *mempool_alloc(mempool_t *pool, gfp_t gfp_mask);
void mempool_free(void *element, mempool_t *pool); //回收对象
void mempool_destroy(mempool_t *pool); //回收内存池//地址的转换 virt_to_phys   virt_to_phys 注意,上述方法仅适用于常规内存,高端内存的虚拟地址与物理地址之间不存在如此简单的换算关系/*********************设备IO*****************/
//arm 端的代码最后调用 io-readsl.S  位于arch/arm/lib/下 
//读写字节端口(8 位宽)
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
//16 
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
//32 长字节
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);//读写一串字节
//insb()从端口 port 开始读 count 个字节端口,并将读取结果写入 addr 指向的内存
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
//16
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
//32
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);//io 内存
//在内核中访问 I/O 内存之前,需首先使用 ioremap()函数将设备所处的物理地址映射到虚拟地址
//通过 ioremap()获得的虚拟地址应该被 iounmap()函数释放
void *ioremap(unsigned long offset, unsigned long size);
void iounmap(void * addr);/*
在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地
址,但是可以使用 Linux 内核的如下一组函数来完成设备内存映射的虚拟地址的读写
*/
//内存映射后的操作
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);//读一串IO
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
//写一串IO
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);//复制IO内存
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);//设置IO内存
void memset_io(void *addr, u8 value, unsigned int count);
//把 I/O 端口映射到内存空间
void *ioport_map(unsigned long port, unsigned int count);
void ioport_unmap(void *addr);// I/O 端口申请 
// 这个函数向内核申请 n 个端口,这些端口从 first 开始,name 参数为设备的名称
struct resource *request_region(unsigned long first, unsigned long n,const char *name);
void release_region(unsigned long start, unsigned long n);// I/O 内存申请
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
void release_mem_region(unsigned long start, unsigned long len);/*
设备 I/O 端口和 I/O 内存访问流程 request_region (如果不映射到内存空间)inb,outbrelease_region	request_region (映射到内存空间)    / request_mem_regionipport_mapioread8,iowrite8 (注意内存映射后使用不是使用inb等方法)ioport_unmaprelease_region	/  release_mem_region*//*将设备地址映射到用户空间
这种能力对于显示适配器一类的设备非常有意义,如果用户空间可直接通过内存
映射访问显存的话,屏幕帧的各点的像素将不再需要一个从用户空间到内核空间的复
制的过程mmap()必须以 PAGE_SIZE 为单位进行映射,实际上,内存只能以页为单位进行映
射,若要映射非 PAGE_SIZE 整数倍的地址范围,要先进行页对齐,强行以 PAGE_SIZE
的倍数大小进行映射当用户调用 mmap()的时候,内核会进行如下处理。
① 在进程的虚拟空间查找一块 VMA。
② 将这块 VMA 进行映射。
③ 如果设备驱动程序或者文件系统的 file_operations 定义了 mmap()操作,则调用
它。
④ 将这个 VMA 插入到进程的 VMA 链表中。*/
//mmap()  下面是一个示例,驱动中实现
static int impd1fb_clcd_mmap(struct clcd_fb *fb, struct vm_area_struct *vma)
{unsigned long start, size;start = vma->vm_pgoff + (fb->fb.fix.smem_start >> PAGE_SHIFT);size = vma->vm_end - vma->vm_start;return remap_pfn_range(vma, vma->vm_start, start, size,vma->vm_page_prot);
}struct vm_area_struct {/* The first cache line has the info for VMA tree walking. */unsigned long vm_start;		/* Our start address within vm_mm. */unsigned long vm_end;		/* The first byte after our end addresswithin vm_mm. */.........struct mm_struct *vm_mm;	/* The address space we belong to. */ /* 所处的地址空间 */pgprot_t vm_page_prot;		/* Access permissions of this VMA. */ /* 访问权限 */unsigned long vm_flags;		/* Flags, see mm.h. */......../* Function pointers to deal with this struct. */const struct vm_operations_struct *vm_ops;/* Information about our backing store: */unsigned long vm_pgoff;		/* Offset (within vm_file) in PAGE_SIZEunits */.........
} __randomize_layout;//后面可以写一个示例,实现 映射 kmalloc()申请的内存到用户空间范例   待完成。。。/*
使用DMA 时要注意DMA 与CACHE一致性问题
解决由于 DMA导致的 Cache 一致性问题的最简单方法是直接禁止 DMA 目标地址范围内内存的 Cache 功能DMA 的硬件使用总线地址而非物理地址,总线地址是从设备角度上看到的
内存地址,物理地址则是从 CPU 角度上看到的未经转换的内存地址(经过转换的为
虚拟地址)
*///内核提供了如下函数用于进行简单的虚拟地址/总线地址转换
unsigned long virt_to_bus(volatile void *address);
void *bus_to_virt(unsigned long address);/*
设备并不一定能在所有的内存地址上执行 DMA 操作,在这种情况下应该通过下
列函数执行 DMA 地址掩码
int dma_set_mask(struct device *dev, u64 mask);
例如,对于只能在 24 位地址上执行 DMA 操作的设备而言,就应该调用dma_set_mask (dev, 0xffffff)。DMA 映射包括两个方面的工作:分配一片 DMA 缓冲区;为这片缓冲区产生设备
可访问的地址。同时,DMA 映射也必须考虑 Cache 一致性问题。内核中提供了以下函数用于分配一个 DMA 一致性的内存区域
void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t*handle, gfp_t gfp);上述函数的返回值为申请到的 DMA 缓冲区的虚拟地址,此外,该函数还通过参数 handle 返回 DMA 缓冲区的总线地址。
handle 的类型为 dma_addr_t,代表的是总线地址。*/
int dma_set_mask(struct device *dev, u64 mask);
void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t*handle, gfp_t gfp);
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,dma_addr_t handle);//申请和释放DMA
int request_dma(unsigned int dmanr, const char * device_id);
void free_dma(unsigned int dmanr);//dma 操作要实际在项目代码中学习/*********************并发控制*****************///原子操作
atomic_t v = ATOMIC_INIT(0); //定义原子变量 v 并初始化为 0
void atomic_set(atomic_t *v, int i); //设置原子变量的值为 i
atomic_read(atomic_t *v); //返回原子变量的值
void atomic_add(int i, atomic_t *v); //原子变量增加 i
void atomic_sub(int i, atomic_t *v); //原子变量减少 i
void atomic_inc(atomic_t *v); //原子变量增加 1
void atomic_dec(atomic_t *v); //原子变量减少 1//对原子变量执行自增、自减和减操作后(注意没有加)测试其是否为 0,为 0 则返回 true,否则返回 false
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);//位原子操作
void set_bit(nr, void *addr);
void clear_bit(nr, void *addr);
void change_bit(nr, void *addr);
test_bit(nr, void *addr);//test_and_xxx_bit(nr, void *addr)操作等同于执行 test_bit(nr, void *addr)后再执行 xxx_bit(nr, void *addr)
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);//自旋锁 基本使用
//定义一个自旋锁
spinlock_t lock;
spin_lock_init(&lock);
spin_lock (&lock) ; //获取自旋锁,保护临界区
//...//临界区
spin_unlock (&lock) ; //解锁/*
尽管用了自旋锁可以保证临界区不受别的 CPU 和本 CPU 内的抢占进程打扰,但
是得到锁的代码路径在执行临界区的时候还可能受到中断和底半部(BH)的影响。
为了防止这种影响,就需要用到自旋锁的衍生。spin_lock()/spin_unlock()是自旋锁机
制的基础,它们和
关中断 local_irq_ disable()/开中断 local_irq_enable()、
关底半部local_bh_disable()/开底半部 local_bh_enable()、
关中断并保存状态字 local_irq_save()/开中断并恢复状态 local_irq_restore()
结合就形成了整套自旋锁机制
*/spin_lock_irq() = spin_lock() + local_irq_disable()
spin_unlock_irq() = spin_unlock() + local_irq_enable()
spin_lock_irqsave() = spin_unlock() + local_irq_save()
spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()
spin_lock_bh() = spin_lock() + local_bh_disable()
spin_unlock_bh() = spin_unlock() + local_bh_enable()//!注意 copy_from_user()、copy_to_user()和 kmalloc()等函数都有可能引起阻塞,因此在自旋锁的占用期间不能调用这些函数//读写锁
//读写锁是一种比自旋锁粒度更小的锁机制,它保留了“自旋”的概念rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 静态初始化 */
rwlock_t my_rwlock;
rwlock_init(&my_rwlock); /* 动态初始化 */void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int write_trylock(rwlock_t *lock);void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);//顺序锁    读写自旋锁
/*
顺序锁(seqlock)是对读写锁的一种优化,若使用顺序锁,读执行单元绝不会被
写执行单元阻塞,也就是说,读执行单元可以在写执行单元对被顺序锁保护的共享资
源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也
不需要等待所有读执行单元完成读操作才去进行写操作。顺序锁有一个限制,它必须要求被保护的共享资源不含有指针,因为写执行单元可能使得指针失效,
但读执行单元如果正要访问该指针,将导致 Oops*/
#include <linux/rwlock.h>void write_seqlock(seqlock_t *sl);
int write_tryseqlock(seqlock_t *sl);
write_seqlock_irqsave(lock, flags);
write_seqlock_irq(lock);
write_seqlock_bh(lock);void write_sequnlock(seqlock_t *sl);
write_sequnlock_irqrestore(lock, flags);
write_sequnlock_irq(lock);
write_sequnlock_bh(lock);unsigned read_seqbegin(const seqlock_t *sl);
read_seqbegin_irqsave(lock, flags);//读执行单元在访问完被顺序锁 s1 保护的共享资源后需要调用该函数来检查,在读
//访问期间是否有写操作。如果有写操作,读执行单元就需要重新进行读操作.
int read_seqretry(const seqlock_t *sl, unsigned iv);
read_seqretry_irqrestore(lock, iv, flags);//用法
do {
seqnum = read_seqbegin(&seqlock_a);
//读操作代码块
//...
} while (read_seqretry(&seqlock_a, seqnum));//RCU的使用//信号量的使用
struct semaphore sem;
static inline void sema_init(struct semaphore *sem, int val)void down(struct semaphore *sem); //获取信号量,不建议使用此函数,因为是 UNINTERRUPTABLE 的睡眠
int down_interruptible(struct semaphore *sem); //可被中断地获取信号量,如果睡眠被信号中断,返回错误-EINTR。
int down_killable(struct semaphore *sem);//可被杀死地获取信号量。如果睡眠被致命信号中断,返回错误-EINTR
int down_trylock(struct semaphore *sem);//尝试原子地获取信号量,如果成功获取,返回0,不能获取,返回1。
int down_timeout(struct semaphore *sem, long jiffies); //在指定的时间jiffies内获取信号量,若超时未获取,返回错误-ETIME
void up(struct semaphore *sem);//释放信号量sem。//读写信号量
rw_semaphore rw_sem; //定义读写信号量
init_rwsem(&rw_sem); //初始化读写信号量
//读时获取信号量
down_read(&rw_sem);
//... //临界资源
up_read(&rw_sem);//写时获取信号量
down_write(&rw_sem);
//... //临界资源
up_write(&rw_sem);//互斥,比信号量的实现互斥 效率要高
struct mutex my_mutex; //定义 mutex
mutex_init(&my_mutex); //初始化 mutex
mutex_lock(&my_mutex); //获取 mutex
//...//临界资源
mutex_unlock(&my_mutex); //释放 mutex//完成量
struct completion my_completion;init_completion(&my_completion);
reinit_completion(&my_completion)//用于等待一个完成量被唤醒
void wait_for_completion(struct completion *c);//唤醒完成量
void complete(struct completion *c);
void complete_all(struct completion *c);/*********************阻塞与非阻塞*****************/
//可以使用等待队列(wait queue)来实现阻塞进程的唤醒
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
DECLARE_WAIT_QUEUE_HEAD (name);
DECLARE_WAITQUEUE(name, tsk);void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t*wait);
void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t*wait);//等待事件
wait_event(queue, condition);
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, condition, timeout); //timeout 以 jiffy 为单位
wait_event_interruptible_timeout(queue, condition, timeout); //唤醒队列,会唤醒以 queue 作为等待队列头的所有等待队列中所有属的进程
//注意(interruptible)成对的使用 
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);sleep_on(wait_queue_head_t *q );
interruptible_sleep_on(wait_queue_head_t *q );//如果进程被其他地方唤醒,将等待队列移出等待队列头/*
在内核中使用set_current_state()函数或_ _add_wait_queue()函数来实现目前进程状态的改变,
直接采用 current->state = TASK_UNINTERRUPTIBLE 类似的赋值语句也是可行的。
通常而言,set_current_state()函数在任何环境下都可以使用,不会存在并发问题,但是效率要低于 __add_ wait_queue();
*///轮询操作 select poll
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);//这个函数并不会引起阻塞//异步操作
//处理FASYNC标志变更的函数
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);static int globalfifo_fasync(int fd, struct file *filp, int mode)
{struct globalfifo_dev *dev = filp->private_data;return fasync_helper(fd, filp, mode, &dev->async_queue);
}//释放信号用的函数
void kill_fasync(struct fasync_struct **fa, int sig, int band);//在设备资源可以获得时, 应该调用kill_fasync() 释放SIGIO信号。 
//在可读时, 第3个参数设置为POLL_IN, 在可写时, 第3个参数设置为POLL_OUT
//SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);static int globalfifo_release(struct inode *inode, struct file *filp)
{globalfifo_fasync(-1, filp, 0); /* 将文件从异步通知列表中删除 */return 0;
}//应用端
signal(SIGIO, signalio_handler);
fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);//异步IO
int aio_read( struct aiocb *aiocbp );
int aio_write( struct aiocb *aiocbp );
int aio_error( struct aiocb *aiocbp );
ssize_t aio_return( struct aiocb *aiocbp );/*
Linux设计中强调的一个基本观点是机制和策略的分离。 机制是做某
样事情的固定步骤、 方法, 而策略就是每一个步骤所采取的不同方式。 机制是相对固定的, 而每个步骤采
用的策略是不固定的。 机制是稳定的, 而策略则是灵活的, 因此, 在Linux内核中, 不应该实现策略
*//*********************字符设备*****************/
struct cdev {struct kobject kobj; /* 内嵌的kobject对象 */struct module *owner; /* 所属模块*/struct file_operations *ops; /* 文件操作结构体*/struct list_head list;dev_t dev; /* 设备号  其中12位为主设备号, 20位为次设备号*/unsigned int count;
};MAJOR(dev_t dev);
MINOR(dev_t dev);
MKDEV(int major, int minor);//Linux内核提供了一组函数以用于操作cdev结构体
void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);/*
在调用cdev_add() 函数向系统注册字符设备之前, 应首先调用register_chrdev_region() 或
alloc_chrdev_region() 函数向系统申请设备号
*/int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); //自动分配设备号//内核空间虽然可以访问用户空间的缓冲区, 但是在访问之前, 一般需要先检查其合法性, 通过
//access_ok(type, addr, size) 进行判断, 以确定传入的缓冲区的确属于用户空间
access_ok(type,addr,size);//access_ok(VERIFY_WRITE, buf, count)
/*
如果要复制的内存是简单类型, 如char、 int、 long等, 则可以使用简单的put_user() 和get_user() 
*/int val; /* 内核空间整型变量*/get_user(val, (int *) arg); /* 用户→内核, arg是用户空间的地址 */
put_user(val, (int *) arg); /* 内核→用户, arg是用户空间的地址 */__get_user(val, (int *) arg); //get_user类似,但没有检测合法性//乱序
/*
在Linux内核中, 定义了读写屏障mb( ) 、 读屏障rmb( ) 、 写屏障wmb( ) 、 以及作用于寄存器读
写的__iormb( ) 、 __iowmb( ) 这样的屏障API。 读写寄存器的readl_relaxed( ) 和readl( ) 、
writel_relaxed( ) 和writel( ) API的区别就体现在有无屏障方面。比如我们通过writel_relaxed( ) 写完DMA的开始地址、 结束地址、 大小之后, 我们一定要调用
writel( ) 来启动DMA*/#define readb(c) ({ u8 __v = readb_relaxed(c); __iormb(); __v; })
#define readw(c) ({ u16 __v = readw_relaxed(c); __iormb(); __v; })
#define readl(c) ({ u32 __v = readl_relaxed(c); __iormb(); __v; })
#define writeb(v,c) ({ __iowmb(); writeb_relaxed(v,c); })
#define writew(v,c) ({ __iowmb(); writew_relaxed(v,c); })
#define writel(v,c) ({ __iowmb(); writel_relaxed(v,c); })writel_relaxed(DMA_SRC_REG, src_addr);
writel_relaxed(DMA_DST_REG, dst_addr);
writel_relaxed(DMA_SIZE_REG, size);
writel (DMA_ENABLE, 1);/*********************设备树相关操作*****************/
//根据兼容属性, 获得设备节点。 遍历设备树中的设备节点, 看看哪个节点的类型、 兼容属性与本函数的输入参数匹配,
//在大多数情况下, from、 type为NULL, 则表示遍历了所有节点
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible);//通过 of_device_id 匹配表来查找指定的节点
struct device_node *of_find_matching_node_and_match(struct device_node *from,const struct of_device_id *matches,const struct of_device_id **match);//读取属性
int of_property_read_u32_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz);
//示例
//if (of_property_read_u32_array(pdev->dev.of_node, "freq-range", range,ARRAY_SIZE(range)) == 0) static inline int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value); 
int of_property_read_string(const struct device_node *np, const char *propname,const char **out_string);//读取字符串数组属性中的第index个字符串
static inline int of_property_read_string_index(const struct device_node *np, const char *propname, int index, const char **output)//如果设备节点np含有propname属性, 则返回true, 否则返回false。 一般用于检查空属性是否存在
static inline bool of_property_read_bool(const struct device_node *np, const char *propname);//内存映射
/*
reg属性
可以直接通过设备节点进行设备内存区间的ioremap() , index是内存段的索引
一些设备驱动通过of_iomap() 而不再通过传统的ioremap() 进行映射
*/
void __iomem *of_iomap(struct device_node *node, int index);//解析中断
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);//获取与节点对应的platform_device
struct platform_device *of_find_device_by_node(struct device_node *np);struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name);//通过节点名字查找指定的节点
struct device_node *of_find_node_by_name(struct device_node *from,const char *name);struct device_node *of_find_node_by_type(struct device_node *from,const char *type);
inline struct device_node *of_find_node_by_path(const char *path);
//struct device_node *of_find_node_opts_by_path(const char *path, const char **opts);//查找父子节点
struct device_node *of_get_parent(const struct device_node *node);
struct device_node *of_get_next_child(const struct device_node *node,struct device_node *prev);static inline int of_property_count_u32_elems(const struct device_node *np,const char *propname);
//int of_property_count_elems_of_size(const struct device_node *np,
//				const char *propname, int elem_size);//用于获取#address-cells 属性值
int of_n_addr_cells(struct device_node *np);
int of_n_size_cells(struct device_node *np);//for_each_property_of_node(of_aliases, pp) { }
for_each_property_of_node/*********************GPIO的操作*****************/
int gpio_request(unsigned gpio, const char *label);
//include/asm-generic/gpio.h
static inline int gpio_direction_input(unsigned gpio);
static inline int gpio_get_value(unsigned int gpio);atomic_set
jiffies
unsigned int jiffies_to_msecs(const unsigned long j); //转换container_of 
//下面为container_of 的实际使用
static ssize_t dts_operator_show(struct device*dev, struct device_attribute *attr, char *buf)
{struct dts_test_dev* dts_dev = container_of(dev,struct dts_test_dev,device); //注意这种用法是错误的 device在结构体中的类型不能是指针if(!dts_dev){printk("dts_operator_show  dts_dev=null ...\n");return -1;}return sprintf(buf, "%s\n", dts_test_dev->data);
}/*********************LIST的使用*****************/
//方式1
LIST_HEAD(name)  //name就是链表头的名字,该宏会自动定义一个名字为name 的 list_head 结构体。
//方式2
struct list_head head;	  //声明链表头
INIT_LIST_HEAD(&head);	  //链表头初始化list_add(struct list_head *new, struct list_head *head);        //加入链表头部
list_add_tail(struct list_head *new, struct list_head *head);   //加入到链表尾部list_for_each(pos, head);    //遍历链表,可以结合list_entry()接口对数据操作
list_for_each_safe(pos, n, head);    //如果在遍历过程中涉及结点删除操作,则需要使用这个接口list_del(struct list_head *entry);
list_empty(const struct list_head *head);     //判断链表是否为空/*********************其它*****************/
//内核提供关闭软中断的锁机抽
local_bh_disable();
locall_bh_enable();//软中断的回调函数不能睡眠//CPUFreq核心层提供了如下API以供SoC注册自身的CPUFreq驱动
int cpufreq_register_driver(struct cpufreq_driver *driver_data);//GPIO驱动
struct gpio_chip gpio;
int gpiochip_add(struct gpio_chip *chip); //注册giop_chip;//常见接口
int gpio_request(unsigned gpio, const char *label);
void gpio_free(unsigned gpio);int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
int gpio_set_debounce(unsigned gpio, unsigned debounce);
int gpio_get_value_cansleep(unsigned gpio);
void gpio_set_value_cansleep(unsigned gpio, int value);
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
int gpio_request_array(const struct gpio *array, size_t num);
void gpio_free_array(const struct gpio *array, size_t num);
int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
int devm_gpio_request_one(struct device *dev, unsigned gpio,unsigned long flags, const char *label);
void devm_gpio_free(struct device *dev, unsigned int gpio);/*
注意: 内核中针对内存、 IRQ、 时钟、 GPIO、 pinctrl、 Regulator都有以devm_开头的API, 使用这部分
API的时候, 内核会有类似于Java的资源自动回收机制, 因此在代码中进行出错处理时, 无须释放相关的资源。
*/#include <linux/pinctrl/consumer.h>
ret = pin_config_set("foo-dev", "FOO_GPIO_PIN", PLATFORM_X_PULL_UP);//根据file 查找cdev
struct file *filp;
struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
struct ap3216c_dev *dev = container_of(cdev, struct ap3216c_dev, cdev);

最后

笔记中有这么一段话:Linux设计中强调的一个基本观点是机制和策略的分离。 机制是做某样事情的固定步骤、 方法, 而策略就是每一个步骤所采取的不同方式。 机制是相对固定的, 而每个步骤采
用的策略是不固定的。 机制是稳定的, 而策略则是灵活的, 因此, 在Linux内核中, 不应该实现策略。

其中的机制与策略面向对象语言的抽象的思想很相似。


http://www.ppmy.cn/news/977753.html

相关文章

Transformer+医学图像最新进展【2023】

Transformer主要用于自然语言处理领域。近年来,它在计算机视觉(CV)领域得到了广泛的应用。医学图像分析(MIA,Medical image analysis)作为机器视觉(CV,Computer Vision)的一个重要分支,也极大地受益于这一最先进的技术。 机构:新加坡国立大学机械工程系、中山大学智能系…

php套用Iframe访问导致cookie跨域session失效问题

一.背景 a网站&#xff08;www.aa.com&#xff09;嵌入b网站 (www.bb.com) 网站&#xff0c;因为跨域原因&#xff0c;其实如果b网站是以aa.com后缀结尾的话是正常的 二.博客参考 https://juejin.cn/post/7123652955282079751 1.上面的试了这个IE的没什么用&#xff08;有可能…

AI Chat 设计模式:9. 命令模式

本文是该系列的第九篇&#xff0c;采用问答式的方式展开&#xff0c;问题由我提出&#xff0c;答案由 Chat AI 作出&#xff0c;灰色背景的文字则主要是我的一些思考和补充。 问题列表 Q.1 介绍下命令模式A.1Q.2 详细说说命令模式适用于啥场景呢A.2Q.3 举一个命令模式的例子&a…

linux LVM磁盘管理

Linux运维过程中经常会遇到扩容的场景&#xff0c;下面做一点简单笔记&#xff0c;所谓好记性不如烂笔头。 1、新建磁盘挂载 &#xff08;1&#xff09;先看看主机上有没有挂载磁盘或挂载的磁盘有没有剩余的。 如下图可以看到这台机器挂了两个盘&#xff0c;一个/dev/sda,这…

Form Generator 扩展子表单组件之表单校验(超详细)

一、form-generator是什么?✨ ⭐️ 🌟 form-generator的作者是这样介绍的:Element UI表单设计及代码生成器,可将生成的代码直接运行在基于Element的vue项目中;也可导出JSON表单,使用配套的解析器将JSON解析成真实的表单。 但目前它提供的组件并不能满足我们在项目中的…

WPF实战学习笔记06-设置待办事项界面

设置待办事项界面 创建待办待办事项集合并初始化 TodoViewModel&#xff1a; using Mytodo.Common.Models; using Prism.Commands; using Prism.Mvvm; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Sy…

Golang 中使用不定数量空格分割字符串的方法

有这样一个使用空格分割字符串的场景&#xff0c;字符串中被分割的子串之间的空格数量不确定&#xff0c;有一个两个或者多个空格&#xff0c;这种场景下&#xff0c;使用最容易想到的 strings.Split 函数就做不到了。本文接下来就介绍几种行之有效的方法。 使用 strings.Fiel…

BERT精读

BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding 论文精读 —— BERT:Pre-training of Deep Bidirectional Transformers for Language Understanding - 知乎 (zhihu.com) pre-training:在一个数据集上训练好一个模型,这个模型主要的目…