一、内核定时器
1、内核定时器
使用方法:
2、系统时钟中断处理函数
1)更新时间
2)检查当前时间片是否耗尽
Linux操作系统是基于时间片轮询的,属于抢占式的内核
3)jiffies++
3、基本概念
1)HZ
HZ决定了1秒钟产生系统时钟中断的次数
2)tick
tick时钟滴答数
tick = 1 / HZ
记录了两次系统时钟中断之间的时间间隔
3)jiffies
记录了自开机以来,系统时钟中断产生的次数
4、内核定时器
1)timer_list
2)操作系统定时器
【1】定义变量
struct timer_list led_timer;
【2】初始化
init_timer(timer)
【3】启动定时器
void add_timer(struct timer_list *timer)
int mod_timer(struct timer_list *timer, unsigned long expires)
【4】停止定时器
int del_timer(struct timer_list *timer)
3)实验
【1】进入工程目录
cd /home/zjd/s5p6818/KERNEL/drivers
【2】创建新的工程
mkdir timer_led
【3】编写程序
vim timer_led.c
#include <linux/init.h> #include <linux/module.h> #include <mach/platform.h> #include <linux/gpio.h> #include <linux/timer.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Zjd"); // step_2 : define the gpio_b26 #define LED0 (PAD_GPIO_B + 26) // step_5 : define the global var struct timer_list led_timer; // step_7 : implement the fun when the time is over void timer_func(unsigned long data) { // step_8 : set the LED0 value gpio_set_value(LED0, data); // step_9 : reverse the LED0 led_timer.data = !data; // step_10 : begin count timer, second is 1s mod_timer(&led_timer, jiffies + 1 * HZ); } int __init timer_led_init(void) { // step_1 : request the gpio gpio_request(LED0, "led0"); // step_3 : set output mode, default value is 1, the turn_off led gpio_direction_output(LED0, 1); // step_4 : initialize the timer init_timer(&led_timer); // set the intervalue timer led_timer.expires = jiffies + 1 * HZ; // set the fun when the timer over led_timer.function = timer_func; // set the param of timer_func led_timer.data = 0; // step_6 : turn on the timer add_timer(&led_timer); return 0; } void __exit timer_led_exit(void) { // step_11 : stop the timer del_timer(&led_timer); // step_12 : destory the gpio gpio_free(LED0); return ; } module_init(timer_led_init); module_exit(timer_led_exit);
【4】编写Makefile
vim Makefile
obj-m += timer_led.o KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel ROOTFS_PATH=/nfs_share/rootfs all: make -C $(KERNEL_PATH) M=$(PWD) modules cp *.ko $(ROOTFS_PATH) clean: make -C $(KERNEL_PATH) M=$(PWD) clean
【5】编译工程
make
【6】下位机测试
telnet 192.168.1.6
insmod timer_led.ko
【7】流水灯
vim water_led.c
#include <linux/init.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/timer.h> #include <mach/platform.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("zjd"); #define LED0 (PAD_GPIO_B + 26) #define LED1 (PAD_GPIO_C + 11) #define LED2 (PAD_GPIO_C + 7) #define LED3 (PAD_GPIO_C + 12) struct timer_list led_timer; unsigned int led[] = {LED0, LED1, LED2, LED3}; void led_function(unsigned long data) { static int pre = 0; static int next = 0; led_timer.data = !data; gpio_set_value(led[pre], led_timer.data); next = (pre + 1) % (sizeof(led) / sizeof(led[0])); led_timer.data = !led_timer.data; gpio_set_value(led[next], led_timer.data); pre = next; mod_timer(&led_timer, jiffies + HZ / 2); } int __init led_drv_init(void) { gpio_request(LED0, "led0"); gpio_request(LED1, "led1"); gpio_request(LED2, "led2"); gpio_request(LED3, "led3"); gpio_direction_output(LED0, 1); gpio_direction_output(LED1, 1); gpio_direction_output(LED2, 1); gpio_direction_output(LED3, 1); init_timer(&led_timer); led_timer.expires = jiffies + HZ / 2; led_timer.function = led_function; led_timer.data = 0; add_timer(&led_timer); return 0; } void __exit led_drv_exit(void) { del_timer(&led_timer); gpio_free(LED0); gpio_free(LED1); gpio_free(LED2); gpio_free(LED3); return ; } module_init(led_drv_init); module_exit(led_drv_exit);
二、内核的竞态与并发
1、介绍
PC机上的串口设备是一种独占式访问的设备(只能有一个进程进行访问),独占式操作的实现是在串口的驱动程序中实现的,我们希望按键设备只允许一个进程进行访问。
2、基础实验
【1】进入工程目录
cd /home/zjd/s5p6818/KERNEL/drivers
【2】创建新的工程
mkdir single_btn
【3】编写程序
vim single_btn.c
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> MODULE_LICENSE("GPL"); //[2]定义全局变量----设备号 dev_t dev; //[4]定义全局变量----cdev对象 struct cdev btn_cdev; //[7]定义全局指针----class类 struct class *btn_cls = NULL; //[d]按照原型实现btn_open int btn_open(struct inode *inode, struct file *fp) { printk("<0>" "enter : %s\n", __func__); return 0; } //[e]按照原型实现btn_close int btn_close(struct inode *inode, struct file *fp) { printk("<0>" "enter : %s\n", __func__); return 0; } //[c]定义全局变量---操作函数集合 struct file_operations btn_fops = { .owner = THIS_MODULE, .open = btn_open, .release = btn_close }; int __init btn_drv_init(void) { //[1]申请设备号 alloc_chrdev_region(&dev, 0, 1, "btn"); //[3]初始化 cdev 对象 cdev_init(&btn_cdev, &btn_fops); //[5]注册 cdev 对象到内核中 cdev_add(&btn_cdev, dev, 1); //[6]自动创建设备文件(创建树枝) btn_cls = class_create(THIS_MODULE, "mybuttons"); //(创建果实) device_create(btn_cls, NULL, dev, NULL, "buttons"); return 0; } void __exit btn_drv_exit(void) { //[8]销毁设备文件(销毁果实) device_destroy(btn_cls, dev); //[9]销毁设备文件依赖的设备树(销毁树枝) class_destroy(btn_cls); //[a]注销 cdev 对象 cdev_del(&btn_cdev); //[b]释放设备号 unregister_chrdev_region(dev, 1); return ; } module_init(btn_drv_init); module_exit(btn_drv_exit);
【4】编写Makefile
vim Makefile
obj-m += signal_btn.o KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel ROOTFS_PATH=/nfs_share/_install all: make -C $(KERNEL_PATH) M=$(PWD) modules cp *.ko $(ROOTFS_PATH) clean: make -C $(KERNEL_PATH) M=$(PWD) clean
【5】编译工程
make
【6】编写应用层程序
mkdir test
cd test
vim btn_test.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(void) { int fd = open("/dev/buttons", O_RDONLY); if(fd < 0) { perror("open()"); return -1; } printf("open success!\n"); while (1) { ; } close(fd); return 0; }
vim Makefile
SRC=btn_test.c OBJ=btn_test ARM_COMPILE=arm-cortex_a9-linux-gnueabi- GCC=gcc ROOTFS_PATH=/nfs_share/_install all: $(ARM_COMPILE)$(GCC) $(SRC) -o $(OBJ) cp $(OBJ) $(ROOTFS_PATH) clean: rm -rf $(OBJ)
【7】编译工程
make
【8】下位机测试
insmod single_btn.ko
./btn_test
telnet 192.168.1.6
./btn_test
【9】总结
我们发现,这两个进程都可以打开这个设备文件,这样与我们的需求不符,我们希望在同一时刻,只能有一个进程访问这个设备文件。
两个进程属于竞争关系,都来抢设备文件这个资源,如何做到同一时刻只能一个进程对设备文件进行访问?(竞态)
3、全局变量
vim single_btn.c
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #define ON 1 #define OFF 0 MODULE_LICENSE("GPL"); //[2]定义全局变量----设备号 dev_t dev; //[4]定义全局变量----cdev对象 struct cdev btn_cdev; //[7]定义全局指针----class类 struct class *btn_cls = NULL; int btn_switch = OFF; //[d]按照原型实现btn_open int btn_open(struct inode *inode, struct file *fp) { printk("<0>" "enter : %s\n", __func__); if (OFF == btn_switch) btn_switch = ON; else return -EAGAIN; return 0; } //[e]按照原型实现btn_close int btn_close(struct inode *inode, struct file *fp) { printk("<0>" "enter : %s\n", __func__); if (ON == btn_switch) btn_switch = OFF; else return -EAGAIN; return 0; } //[c]定义全局变量---操作函数集合 struct file_operations btn_fops = { .owner = THIS_MODULE, .open = btn_open, .release = btn_close }; int __init btn_drv_init(void) { //[1]申请设备号 alloc_chrdev_region(&dev, 0, 1, "btn"); //[3]初始化 cdev 对象 cdev_init(&btn_cdev, &btn_fops); //[5]注册 cdev 对象到内核中 cdev_add(&btn_cdev, dev, 1); //[6]自动创建设备文件(创建树枝) btn_cls = class_create(THIS_MODULE, "mybuttons"); //(创建果实) device_create(btn_cls, NULL, dev, NULL, "buttons"); return 0; } void __exit btn_drv_exit(void) { //[8]销毁设备文件(销毁果实) device_destroy(btn_cls, dev); //[9]销毁设备文件依赖的设备树(销毁树枝) class_destroy(btn_cls); //[a]注销 cdev 对象 cdev_del(&btn_cdev); //[b]释放设备号 unregister_chrdev_region(dev, 1); return ; } module_init(btn_drv_init); module_exit(btn_drv_exit);
注意:
由于cpu调度策略的不确定型,这种使用全局变量的方法解决竞态是不靠谱的。
4、竞态
1)简介
竞态就是竞争的状态,竞争的是共享资源,在硬件设备的角度说,所谓的共享资源指的是:UART、LCD、声卡、网卡
2)临界区
访问共享资源的代码就是临界区
3)产生竞态的原因
【1】多核处理器
【2】多任务之间的抢占
【3】中断和任务之间的抢占
【4】中断和中断之间的抢占(中断优先级,优先级编号越小,优先级越高)
4)解决竞态的策略
【1】中断屏蔽
【2】原子操作
位原子操作
整型原子操作
【3】自旋锁
【4】信号量
5)中断屏蔽
可以解决【2】【3】【4】产生竞态的原因
【1】中断操作
local_irq_enable() // 中断使能
local_irq_disable() // 中断失能
【2】执行代码
在中断失能和中断使能之间执行临界区的资源
注意:
关中断的时间要特别的短,一但过长,内核将直接崩溃,因为Linux内核中有很多重要的机制是靠中断实现的。写驱动时,不建议用这种方式。
6)原子操作
原子操作是不可分割的,要么都执行,要么不执行。在执行完毕之前不会被任何其他的任务或事件中断。
【1】位原子操作
static inline void set_bit(int nr, volatile unsigned long *addr)
static inline void clear_bit(int nr, volatile unsigned long *addr)
static inline int test_and_set_bit(int nr, volatile unsigned long *addr)
static inline int test_and_clear_bit(int nr, volatile unsigned long *addr)
static inline void change_bit(int nr, volatile unsigned long *addr)
【2】整型原子操作
atomic_t // 整型原子结构体
static inline void atomic_add(int i, atomic_t *v) // 给整型原子变量 + i
static inline void atomic_sub(int i, atomic_t *v) // 给整型原子变量 - i
static inline void atomic_inc(atomic_t *v) // 给整型原子变量 + 1
static inline void atomic_dec(atomic_t *v) // 给整型原子变量 - 1
【3】实验
vim single_btn.c
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #define ON 1 #define OFF 0 MODULE_LICENSE("GPL"); //[2]定义全局变量----设备号 dev_t dev; //[4]定义全局变量----cdev对象 struct cdev btn_cdev; //[7]定义全局指针----class类 struct class *btn_cls = NULL; // define global variable //int btn_switch = OFF; // define int atomic_t variable atomic_t btn_tv; //[d]按照原型实现btn_open int btn_open(struct inode *inode, struct file *fp) { printk("<0>" "enter : %s\n", __func__); #if 0 if (OFF == btn_switch) btn_switch = ON; else return -EAGAIN; #else if (!atomic_dec_and_test(&btn_tv)) { atomic_inc(&btn_tv); return -EAGAIN; } #endif return 0; } //[e]按照原型实现btn_close int btn_close(struct inode *inode, struct file *fp) { printk("<0>" "enter : %s\n", __func__); #if 0 if (ON == btn_switch) btn_switch = OFF; else return -EAGAIN; #else atomic_inc(&btn_tv); #endif return 0; } //[c]定义全局变量---操作函数集合 struct file_operations btn_fops = { .owner = THIS_MODULE, .open = btn_open, .release = btn_close }; int __init btn_drv_init(void) { //[1]申请设备号 alloc_chrdev_region(&dev, 0, 1, "btn"); //[3]初始化 cdev 对象 cdev_init(&btn_cdev, &btn_fops); //[5]注册 cdev 对象到内核中 cdev_add(&btn_cdev, dev, 1); //[6]自动创建设备文件(创建树枝) btn_cls = class_create(THIS_MODULE, "mybuttons"); //(创建果实) device_create(btn_cls, NULL, dev, NULL, "buttons"); // initialize int atomic_t variable atomic_set(&btn_tv, 1); return 0; } void __exit btn_drv_exit(void) { //[8]销毁设备文件(销毁果实) device_destroy(btn_cls, dev); //[9]销毁设备文件依赖的设备树(销毁树枝) class_destroy(btn_cls); //[a]注销 cdev 对象 cdev_del(&btn_cdev); //[b]释放设备号 unregister_chrdev_region(dev, 1); return ; } module_init(btn_drv_init); module_exit(btn_drv_exit);
当上位机对btn设备文件访问完毕时
注意:
由于对整型变量的操作都是原子化的,所以这种方案是可行的
7)自旋锁
【1】核心数据结构
spinlock_t
【2】使用方法
1】获取自旋锁
2】执行临界区代码,访问共享资源
3】释放自旋锁
【3】特点
1】自旋锁只有一个持有单元
2】若试图获取一个已经被其它执行单元持有的自旋锁,将会阻塞等待,直到原持有方释放自旋锁。
【4】使用步骤
【1】定义变量
spinlock_t btn_lock;
【2】初始化
spin_lock_init(_lock)
【3】获取锁
static inline void spin_lock(spinlock_t *lock)
【4】释放自旋锁
static inline void spin_unlock(spinlock_t *lock)
【5】实验
vim single_btn.c
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #define ON 1 #define OFF 0 // controller the different ways though to define value //#define ATOMIC #define SPINLOCK MODULE_LICENSE("GPL"); //[2]定义全局变量----设备号 dev_t dev; //[4]定义全局变量----cdev对象 struct cdev btn_cdev; //[7]定义全局指针----class类 struct class *btn_cls = NULL; #if defined(ATOMC) // define int atomic_t variable atomic_t btn_tv; #elif defined(SPINLOCK) // define global variable the sources of compare int btn_switch = OFF; // define the reverse lock struct spinlock btn_lock; #endif //[d]按照原型实现btn_open int btn_open(struct inode *inode, struct file *fp) { printk("<0>" "enter : %s\n", __func__); #if defined(SPINLOCK) if (OFF == btn_switch) { spin_lock(&btn_lock); btn_switch = ON; spin_unlock(&btn_lock); } else return -EAGAIN; #elif defined(ATOMIC) if (!atomic_dec_and_test(&btn_tv)) { atomic_inc(&btn_tv); return -EAGAIN; } #endif return 0; } //[e]按照原型实现btn_close int btn_close(struct inode *inode, struct file *fp) { printk("<0>" "enter : %s\n", __func__); #if defined(SPINLOCK) spin_lock(&btn_lock); btn_switch = OFF; spin_unlock(&btn_lock); #elif defined(ATOMIC) atomic_inc(&btn_tv); #endif return 0; } //[c]定义全局变量---操作函数集合 struct file_operations btn_fops = { .owner = THIS_MODULE, .open = btn_open, .release = btn_close }; int __init btn_drv_init(void) { //[1]申请设备号 alloc_chrdev_region(&dev, 0, 1, "btn"); //[3]初始化 cdev 对象 cdev_init(&btn_cdev, &btn_fops); //[5]注册 cdev 对象到内核中 cdev_add(&btn_cdev, dev, 1); //[6]自动创建设备文件(创建树枝) btn_cls = class_create(THIS_MODULE, "mybuttons"); //(创建果实) device_create(btn_cls, NULL, dev, NULL, "buttons"); #if defined(ATOMIC) // initialize int atomic_t variable atomic_set(&btn_tv, 1); #elif defined(SPINLOCK) // initialize reverse lock variable spin_lock_init(&btn_lock); #endif return 0; } void __exit btn_drv_exit(void) { //[8]销毁设备文件(销毁果实) device_destroy(btn_cls, dev); //[9]销毁设备文件依赖的设备树(销毁树枝) class_destroy(btn_cls); //[a]注销 cdev 对象 cdev_del(&btn_cdev); //[b]释放设备号 unregister_chrdev_region(dev, 1); return ; } module_init(btn_drv_init); module_exit(btn_drv_exit);
注意:
1】持有自旋锁的事件要尽量短,临界区中不应该出现引起阻塞或休眠的函数
会导致内核吐核
2】避免死锁情况的发生
static inline int spin_trylock(spinlock_t *lock) // 获取不成功,返回错误
8)信号量
当临界区的代码需要长时间执行时,我们可以使用信号量机制
【1】核心数据结构
struct semaphore
【2】使用方法
1】获取信号量
2】执行临界区代码,访问共享资源
3】释放信号量
【3】特点
1】信号量可以持有多个单元
2】获取信号量的本质就是引用计数 - 1(count--)
3】释放信号量的本质就是引用计数 + 1(count++)
4】最小减到0,如果到0了,再去获取信号量会失败,睡眠等待
【4】使用步骤
【1】定义信号量
struct semaphore btn_sem;
【2】初始化
static inline void sema_init(struct semaphore *sem, int val)
【3】获取信号量
1】void down(struct semaphore *sem)
2】int down_interruptible(struct semaphore *sem)
3】int down_killable(struct semaphore *sem)
【4】释放信号量
void up(struct semaphore *sem)
【5】实验
vim single_btn.c
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/delay.h> // only one sources #define SOURCE_NUM 1 #define ON 1 #define OFF 0 // controller the different ways though to define value //#define ATOMIC //#define SPINLOCK #define SEMAPHORE typedef int source_t; MODULE_LICENSE("GPL"); //[2]定义全局变量----设备号 dev_t dev; //[4]定义全局变量----cdev对象 struct cdev btn_cdev; //[7]定义全局指针----class类 struct class *btn_cls = NULL; #if defined(ATOMC) // define int atomic_t variable atomic_t btn_tv; #elif defined(SPINLOCK) // define global variable the sources of compare int btn_switch = OFF; // define the reverse lock struct spinlock btn_lock; #elif defined(SEMAPHORE) // define global variable the sources of compare source_t src_num = SOURCE_NUM; // define the semaphore struct semaphore btn_sem; #endif //[d]按照原型实现btn_open int btn_open(struct inode *inode, struct file *fp) { printk("<0>" "enter : %s\n", __func__); #if defined(SEMAPHORE) down(&btn_sem); mdelay(6000); // delay 6s if (!src_num--) { src_num++; up(&btn_sem); return -EAGAIN; } up(&btn_sem); #elif defined(SPINLOCK) if (OFF == btn_switch) { spin_lock(&btn_lock); msleep(100); btn_switch = ON; spin_unlock(&btn_lock); } else return -EAGAIN; #elif defined(ATOMIC) if (!atomic_dec_and_test(&btn_tv)) { atomic_inc(&btn_tv); return -EAGAIN; } #endif return 0; } //[e]按照原型实现btn_close int btn_close(struct inode *inode, struct file *fp) { printk("<0>" "enter : %s\n", __func__); #if defined(SEMAPHORE) up(&btn_sem); src_num++; #elif defined(SPINLOCK) spin_lock(&btn_lock); btn_switch = OFF; spin_unlock(&btn_lock); #elif defined(ATOMIC) atomic_inc(&btn_tv); #endif return 0; } //[c]定义全局变量---操作函数集合 struct file_operations btn_fops = { .owner = THIS_MODULE, .open = btn_open, .release = btn_close }; int __init btn_drv_init(void) { //[1]申请设备号 alloc_chrdev_region(&dev, 0, 1, "btn"); //[3]初始化 cdev 对象 cdev_init(&btn_cdev, &btn_fops); //[5]注册 cdev 对象到内核中 cdev_add(&btn_cdev, dev, 1); //[6]自动创建设备文件(创建树枝) btn_cls = class_create(THIS_MODULE, "mybuttons"); //(创建果实) device_create(btn_cls, NULL, dev, NULL, "buttons"); #if defined(SEMAPHORE) // initialize semaphore variable sema_init(&btn_sem, 1); #elif defined(ATOMIC) // initialize int atomic_t variable atomic_set(&btn_tv, 1); #elif defined(SPINLOCK) // initialize reverse lock variable spin_lock_init(&btn_lock); #endif return 0; } void __exit btn_drv_exit(void) { //[8]销毁设备文件(销毁果实) device_destroy(btn_cls, dev); //[9]销毁设备文件依赖的设备树(销毁树枝) class_destroy(btn_cls); //[a]注销 cdev 对象 cdev_del(&btn_cdev); //[b]释放设备号 unregister_chrdev_region(dev, 1); return ; } module_init(btn_drv_init); module_exit(btn_drv_exit);
9)区别
在解决竞态问题时:
【1】信号量保护的临界区对执行的时间的长短没有要求
自旋锁保护的临界区要求执行速度尽量快
执行时间一旦过长,造成另外一个进程获取锁不成功,内核就会崩溃
此外,多核系统时,自旋锁原地自旋,系统性能下降
【2】获取信号量不成功,进程睡眠等待
获取自旋锁不成功,进程原地自旋