您的位置:首页 > 运维架构 > Linux

[置顶] Linux内核开发一些系统函数的说明整理

2016-10-30 14:34 330 查看
高12位为主设备号,低20位为次设备号,从一个inode中获取主次设备号的函数:
unsigned int iminor(struct inode *inode)
unsigned int imajor(struct inode *inode)

sysfs下的sys目录:
bus目录包含系统中所有的总线类型,class目录包含系统中的设备类型(如网卡设备,声卡设备,输入设备等)。

设备号:dev_t dev = MKDEV(int major, int minor)
MAJOR(dev_t dev)
MINOR(dev_t dev)

操作cdev结构体的函数:
void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void); ------->用于动态申请一个cdev内存
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t , unsigned);
void cdev_del(struct cdev *);

申请和释放设备号
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);
void unregister_chrdev_region(dev_t from, unsigned count);

内核和用户空间的内存复制函数:
unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);
unsigned long copy_to_user(void __user *to, const void *from, unsigned long count);
如果是简单的类型复制,如char, int, long等,则可以使用简单的put_user()和get_user(),如:get_user(val, (int *)arg); /*用户--->内核*/

整形原子操作
1.设置原子操作
void atomic_set(atomic_t *v, int i); /*设置原子变量的值为i*/
atomic_t v = ATOMIC_INIT(0); /*定义原子变量V并初始化为0*/

2.获取原子变量的值
atomic_read(atomic_t *v); //返回原子变量的值

3.原子变量加/减

void atomic_add(int i, atomic_t *v); //原子变量增加i
void atomic_sub(int i, atomic_t *v);//原子变量减少i

4.原子变量自增自减
void atomic_inc(atomic_t *v) //原子变量增加1
void atomic_dec(atomic_t *v) //原子变量减少1

5.操作并测试

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); //测试其值是否为0,为0返回true,否则返回false。

6. 操作并返回
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) //操作后返回新的值

位原子操作
1.设置位
void set_bit(nr, void *addr); //设置addr地址的第nr位,所设置位既是将位置1

2.清除位

void clear_bit(nr, void *addr);

3.改变位
void change_bit(nr, void *addr) //对addr地址的nr位进行反置

自旋锁
1.定义自旋锁
spinlock_t lock;

2.初始化自旋锁
spin_lock_init(lock);

3.获得自旋锁
spin_lock(lock);//该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将在那里自旋,直到该自旋锁的保持者释放。

4.spin_trylock(lock) //该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回true,否则立即返回false,实际上不再“在原地打转”。

5.spin_unlock(lock) //该宏释放自旋锁lock,他与spin_trylock或spin_lock配的使用。

自旋锁的一般使用:
/*定义一个自旋锁*/
spinlock_t lock;

spin_lock_init(&lock);

spin_lock(&lock); /*获取自旋锁,保护临界区*/
.../*临界区*/
spin_unlock(&lock); /*解锁*/

spin_lock_irq = spin_lock + local_irq_disable
spin_unlock_irq = spin_unlock() + local_irq_enable()
spin_lock_irqsave() = spin_lock() + 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()

信号量
1.定义信号量
struct semaphore sem;

2.初始化信号量
void sema_init(struct semaphore *sem, int val); //该函数初始化信号量,并设置信号量sem的值为val。

3.获取信号量
void down(struct semaphore *sem);//该函数用于获得信号量sem,它会导致睡醒,因此不能在中断上下文中使用。

int down_interruptible(struct semaphore *sem);
说明:该函数功能与down 类似,不同之处为,应为down()进入睡眠状态的进程不能被信号打断,但因为down_interruptible()
进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这时候函数的返回值非0。

int down_trylock(struck semaphore *sem);
说明:该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则返回非0值。它不会导致调用者睡眠,可以在中断上下文中使用。

4.释放信号量
void up(struct semaphore *sem); //该函数释放信号量sem,唤醒等待着。

互斥体
1.定义和初始化互斥体
struct mutex my_mutex;
mutex_init(&my_mutex);

2.获取互斥体
void mutex_lock(struct mutex *lock);
int mutex_lock_interruptible(struct mutex *lock);
int mutex_trylock(struct mutex *lock); //用于尝试获得mutex,获取不到mutex时不会引起进程睡眠。

3.释放互斥体
void mutex_unlock(struct mutex *lock);

等待队列

1.定义“等待队列头部”
wait_queue_head_t my_queue;

2.初始化“等待队列头部”
init_waitqueue_head(&my_queue);

DECLARE_WAIT_QUEUE_HEAD(name) //定义并初始化等待队列头部的“快捷方式”

3.定义等待队列元素
DECLARE_WAITQUEUE(name, task) //该宏用于定义并初始化一个名为name的等待队列元素。

4.添加或移除等待队列
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait); //用于将等待队列元素wait添加到等待队列头部q指向的双向链表中
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

5.等待事件
wait_event(queue, condition);
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, condition, timeout);
wait_event_interruptible_timeout(queue, condition, timeout);

6.唤醒队列
void wake_up(wait_queue_head_t *queue); //可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程
void wake_up_interruptible(wait_queue_heat_t *queue); //可唤醒处于TASK_UNINTERRUPTIBLE的进程

7.在等待队列上睡眠

sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_heat_t *q);

sleep_on()应该与wake_up()成对使用

中断
1. extern int irq_set_affinity(unsigned int irq, const struct cpumask *m);
irq_set_affinity(irq, cpumask_of(i)); //把中断irq设定到CPU i 上去

2.申请中断
int request_irq(unsingned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);

说明:handler是向系统登记的中断处理函数(顶半部),flags为出发方式:IRQF_TRIGGER_RISING, IRQF_TRIGGER_FALLING, IRQF_TRIGGER_HIGH,IRQF_TRIGGER_LOW等
返回值说明:成功返回0,返回-EINVAL表示中断号无效或者处理函数的指针为NULL,返回-EBUSY表示中断已经被占用且不能被共享。

typedef irqreturn_t (*irq_handler_t)(int, void *);
typedef int irqreturn_t;

3. 释放irq
void free_irq(unsinged int irq, void *dev_id);

4. 使能和屏蔽中断
void disable_irq(int irq); //等待目前的中断处理完成
void disable_irq_nosync(int irq); //立即返回
void enable_irq(int irq);

#define local_irq_save(flags) ... //将目前的中断状态保留在flag中
void local_irq_disable(void); //直接禁止中断而不保存状态

相对应的恢复中断:
#define lacal_irq_restore(flags)
void loacal_irq_enable(void);

5.中断底半部分机制
LINUX实现底半部分的机制主要有tasklet, 工作队列,软中断和线程化irq。

5.1 tasklet
它的执行上下文是软中断,执行时机通常是顶半部返回的时候。
void my_tasklet_func(unsigned long);/*定义一个处理函数*/
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data); /*定义一个tasklet结构my_tasklet,与my_tasklet_func(data)相关联*/

在需要使用tasklet时只需要调用函数tasklet_schedule(&y_tasklet);

5.2 工作队列
工作队列的执行上下文是内核线程,因此可以调度和睡眠。工作队列一般用来做滞后的工作,比如在中断里面要做很多事,但是比较耗时,这时就可以把耗时的工作放到工作队列
struct work_struct my_wq; /*定义一个工作队列*/
void my_wq_func(struct work_struct *work)

初始化函数:INIT_WORK(&my_wq, my_wq_func);/*初始化工作队列,并将其与处理函数绑定*/
schedule_work(&my_wq); /*调度工作队列执行*/
注,调用完毕后系统会释放此函数,所以如果想再次执行的话,就再次调用schedule_work()即可。 另外,内核必须挂载文件系统才可以使用工作队列。
我的理解是:工作队列也属于调度,如果内核挂了,他就不调度了,当然就不能用工作队列了。

5.3 软中断
表征一个软中断:
struct softirq_action
{
void(*action)(struct softirq_action *);
};
使用extern void open_softirq(int nr, void (*action)(struct softirq_action *));可以注册软中断的处理函数
使用extern void raise_softirq(unsigned int nr);可以出发一个软中断

软中断和tasklet运行于中断上下文,仍然属于原子上下文的一种,而工作队列则运行于进程上下文。因此,软中断和tasklet处理函数时不
允许睡眠,而工作队列处理函数中允许睡眠。
loacl_bh_disable()和loacl_bh_enable()是内核中用于禁止和使能软中断及tasklet底半部分机制的函数。

5.4 threaded_irq

int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id)

IRQF_SHARED 共享中断时,dev_id不能为空,因为释放irq时要区分哪个共享中断
irq:中断号
handler:发生中断时首先要执行的硬中断处理函数,这个函数可以通过返回 IRQ_WAKE_THREADED唤醒中断线程,也可返回IRQ_HANDLE不执行中断线程
thread_fn : 中断线程,类似于中断下半部
后三个参数与request_irq中的一致
优点:
1)减少 kernel 延迟时间
2)免处理中断时要分辨是在硬体中断或软体中断?
3)更容易为kernel 中断处理除错,可能可完全取代tasklet 原本的中断处理分上半部(硬体中断处理,必须关闭中断无法处理新的中断)跟下半部(
软体中断处理),因此上半部的硬体中断处理必须尽可能简短,让系统反应速度更快。

request_threaded_irq 是在将上半部的硬件中断处理缩短为只确定硬体中断来自我们要处理的装置,唤醒kernel thread 执行后续中断任务。

缺点:
对于非irq 中断的kernel threads ,需要在原本task_struct 新增struct irqaction 多占 4/8 bytes 记忆体空间 linux kernel 2.6.29
之后(2.6.30)加入request_threaded_irq 跟传统top/bottom havles 的差异是threaded_irq 受Linux kernel system 的 process scheduling
控制,不会因为写错的bottom half 代码造成整个系统延迟的问题。也可以透过RT/non RT 跟nice 等工具调整各个thread 优先权,丢给使用
率较低的cpu 以及受惠于kernel 原本可以对threads 做的各种控制,包括但不限于sleep, lock, allocate 新的记忆体区块。受惠最大的是
shared irq line 的多个中断处理。除了可以加速共享中断造成的延迟,threaded_irq 也可以降低在同一段程式码处理多个装置中断的复杂度。

threaded irq 在使用性上也比tasklet(接着top half 直接执行,无法sleep) /workqueue(kernel context?) 等需要在top half 增加跟bottom
half 连结与沟通的麻烦。

内核定时器
1.内核定时器结构体:
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;
unsigned long expires; //定时器到期满的时间jiffies
struct tvec_base *base;

void (*function)(unsigned long); //当定时器期满后,这个函数将会被执行
unsigned long data; //传入的参数

int slack;

#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};

2.初始化定时器
原型:
#include <linux/timer.h>

void init_timer (struct timer_list *timer);
说明:
初始化 timer 结构体变量,使其能够注册到内核定时器目录上。
函数init_timer()主要设置该内核定时器归属系统中哪一个处理,并初始化内核定时器链表指针的next域为NULL

3. 增加定时器
void add_timer(struct timer_list *timer) //注册内核定时器,将定时器加入到内核动态定时器链表里

4.删除定时器
int del_timer(struct timer_list *timer);

5.修改定时器的expire
int mod_timer(struct timer_list *timer, unsigned long expires); //修改定时器的到期时间

6.长延时
time_before(a,b)和time_after(a, b)

输入设备

输入核心提供了底层的输入设备驱动程序所需的API,如分配和释放一个输入设备
struct input_dev *input_allocate_device(void) // 返回的是一个输入设备,struct input_dev结构体代表一个输入设备
void input_free_device(struct input_dev *dev);

注册和注销输入设备的接口的函数:
int _must_check input_register_device(struct input_dev *);
void input_unregister(stuct input_dev *);

报告输入时间的函数:
Void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value);//报告指定type,code的输入事件
Void input_report_key(struct input_dev *dev,unsigned int code,int value);//报告键值
Void input_report_rel(struct input_dev *dev,unsigned int code,int value);//报告相对坐标
Void input_report_abs(struct input_dev *dev,unsigned int code,int value);//报告绝对坐标
Void input_sync(struct input_dev *dev);//报告同步事件

struct input_event结构体:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};

Linux中输入设备的事件类型有(这里只列出了常用的一些,更多请看linux/input.h中):

EV_SYN 0x00 同步事件
EV_KEY 0x01 按键事件
EV_REL 0x02 相对坐标
EV_ABS 0x03 绝对坐标
EV_MSC 0x04 其它
EV_LED 0x11 LED
EV_SND 0x12 声音
EV_REP 0x14 Repeat
EV_FF 0x15 力反馈
~~~~~~~~~~~~~~~~~~~~~~~~
EV_PWR 电源
EV_FF_STATUS 状态

未完待续........ ,后续整理一并罗列出来。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: