一、内核的同步机制
1.1 为什么要有内核的同步的机制
比如现在我同时打开一个音频文件和一个视频文件,音频和视频文件都需要使用声卡,假如内核里边没有做同步,就会发现声音是杂乱的,我们现在之所以能听出来是两个声音 因为内核有了同步机制
1.2 内核同步机制的手段
信号量、原子操作、自旋锁、异步通知
二、信号量
2.1 什么叫做信号量
在系统阶段,IPC通讯里边已经讲解过信号量,主要用于进程间的同步与互斥
多线程中的信号灯:主要用于线程间的同步与互斥
今天第三次学习信号量,主要用于内核的同步与互斥
信号量:
本质上就是一个可以操作的数,这个数可以进行加或者是减的操作,加的时候相当于信号的释放,减的时候相当于信号的消耗,当信号量消耗到0的时候 表示资源已经被消耗完,除非当别的程序释放了信号量
2.2 信号量相关的API
2.2.1 信号量的创建
① 静态创建
#define DEFINE_SEMAPHORE(name) struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
参数:name:信号量的核心结构体的名字
静态创建的时候会默认的将信号量的值设置成1
② 动态创建
函数的功能:动态的创建一个信号量
函数的头文件:linux/semaphore.h
函数的原型:void sema_init(struct semaphore *sem, int val)
函数的参数:struct semaphore *sem:信号量的核心结构体
int val:信号量的初值
函数的返回值:无
//信号量的核心结构体
struct semaphore {
raw_spinlock_t lock;
unsigned int count;//信号量的值 当信号量的值为正数的时候 表示信号量可用;当信号量的值为0的时候表示信号量不可以用
struct list_head wait_list;
};
2.2.2 信号量的操作
头文件:linux/semaphore.h
信号量的消耗 (P操作):
void down(struct semaphore *sem)
阻塞的消耗一个信号量,当信号量消耗不成功,就会陷入阻塞,直到有其他的程序释放了信号
int down_interruptible(struct semaphore *sem)
阻塞的请求一个信号量,当成功请求到信号后马上返回,当请求失败时会陷入阻塞,直到有其他的程序释放了信号,或者是产生了中断
int down_trylock(struct semaphore *sem)
非阻塞的消耗一个信号量,假如信号量消耗成功 返回0,假如信号量消耗失败 返回1
信号量的释放 (V操作):
void up(struct semaphore *sem)
释放信号量,对信号量的成员count进行加1
2.3 代码示例
① 问题代码
/***********************************************************
文件名:led.c
功能:依次打开四个led灯
***********************************************************/
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/workqueue.h>
#include<linux/miscdevice.h>//杂项的头文件
#include<linux/gpio.h>
#include<linux/fs.h>
struct miscdevice mymisc;
struct file_operations myfops;
int count=0;
int myopen (struct inode *inode, struct file *file)
{
gpio_set_value(EXYNOS4X12_GPM4(count),0);
printk("%d号灯已经被打开\n",count);
count++;
return 0;
}
int myclose (struct inode *inode, struct file *file)
{
gpio_set_value(EXYNOS4X12_GPM4(count),0);
printk("%d号灯已经被关闭\n",count);
count--;
return 0;
}
static int __init myled_init(void)
{
myfops.owner=THIS_MODULE;
myfops.open=myopen;
myfops.release=myclose;
mymisc.minor=255;
mymisc.name="led";
mymisc.fops=&myfops;
misc_register(&mymisc);
return 0;
}
static void __exit myled_exit(void)
{
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
#include<stdio.h> //app.c 文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
int fd;
while(1)
{
fd=open("/dev/led",O_RDWR);
sleep(2);
}
return 0;
}
obj-m += led.o //Makefile 文件
KDIR:=/home/ldw/core/linux-3.5
all:
make -C $(KDIR) M=$(PWD) modules
arm-linux-gcc app.c -o app
clean:
rm -rf *.ko *.o *.mod.c *.order *.symvers

这种方法,3号灯之后就已经不对了,不知道操作到哪了,它还在一直地循环,正常的话,灯只能打开四次,这样显然有问题,这就需要用到内核同步机制中的东西。
② 使用信号量之后的代码
/***********************************************************
文件名:led_sem.c
功能:依次打开四个led灯
***********************************************************/
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/workqueue.h>
#include<linux/miscdevice.h>//杂项的头文件
#include<linux/gpio.h>
#include<linux/fs.h>
#include<linux/semaphore.h>
struct miscdevice mymisc;
struct file_operations myfops;
int count=0;
struct semaphore mysem;
int myopen (struct inode *inode, struct file *file)
{
/*
应该先消耗信号量,然后再执行操作:如果能消耗完,才让他去执行打开灯的操作,如果阻塞在这了,就不再执行打开灯的操作了
*/
down(&mysem);//在打开的时候,消耗信号量。
gpio_set_value(EXYNOS4X12_GPM4(count),0);
printk("%d号灯已经被打开\n",count);
count++;
return 0;
}
int myclose (struct inode *inode, struct file *file)
{
gpio_set_value(EXYNOS4X12_GPM4(count),0);
printk("%d号灯已经被关闭\n",count);
count--;
//在哪里去释放一个信号量:在灯完全使用完了,再去释放信号量,也就是count--结束之后
up(&mysem);
return 0;
}
static int __init myled_init(void)
{
sema_init(&mysem, 4);//有四个led灯,也就是至少要保护四个信号量
myfops.owner=THIS_MODULE;
myfops.open=myopen;
myfops.release=myclose;
mymisc.minor=255;
mymisc.name="led";
mymisc.fops=&myfops;
misc_register(&mymisc);
return 0;
}
static void __exit myled_exit(void)
{
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
#include<stdio.h> //app.c 文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
int fd;
while(1)
{
fd=open("/dev/led",O_RDWR);
sleep(2);
}
return 0;
}
obj-m += led_sem.o //Makefile 文件
KDIR:=/home/ldw/core/linux-3.5
all:
make -C $(KDIR) M=$(PWD) modules
arm-linux-gcc app.c -o app
clean:
rm -rf *.ko *.o *.mod.c *.order *.symvers

这种方式,3号灯之后就不再打印了,也就是阻塞了,怎么解除阻塞呢?看下一个程序(增加一个线程)
③ 3号灯5秒之后关闭,然后再打开
/***********************************************************
文件名:led_sem.c
功能:依次打开四个led灯,5秒后关闭3号灯,再打开
***********************************************************/
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/workqueue.h>
#include<linux/miscdevice.h>//杂项的头文件
#include<linux/gpio.h>
#include<linux/fs.h>
#include<linux/semaphore.h>
struct miscdevice mymisc;
struct file_operations myfops;
int count=0;
struct semaphore mysem;
int myopen (struct inode *inode, struct file *file)
{
/*
应该先消耗信号量,然后再执行操作:如果能消耗完,才让他去执行打开灯的操作,如果阻塞在这了,就不再执行打开灯的操作了
*/
down(&mysem);//在打开的时候,消耗信号量。
gpio_set_value(EXYNOS4X12_GPM4(count),0);
printk("%d号灯已经被打开\n",count);
count++;
return 0;
}
int myclose (struct inode *inode, struct file *file)
{
gpio_set_value(EXYNOS4X12_GPM4(count),0);
printk("%d号灯已经被关闭\n",count);
count--;
//在哪里去释放一个信号量:在灯完全使用完了,再去释放信号量,也就是count--结束之后
up(&mysem);
return 0;
}
static int __init myled_init(void)
{
sema_init(&mysem, 4);//有四个led灯,也就是至少要保护四个信号量
myfops.owner=THIS_MODULE;
myfops.open=myopen;
myfops.release=myclose;
mymisc.minor=255;
mymisc.name="led";
mymisc.fops=&myfops;
misc_register(&mymisc);
return 0;
}
static void __exit myled_exit(void)
{
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
#include<stdio.h> //app.c 文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<pthread.h>
int fd;
void *myfunc(void *arg)
{
sleep(5);
close(fd);
}
int main()
{
int count;
pthread_t pd;
while(1)
{
fd=open("/dev/led",O_RDWR);
sleep(2);
if(count==2)
{
pthread_create(&pd,NULL,myfunc,NULL);
}
count++;
}
return 0;
}
obj-m += led_sem.o //Makefile 文件
KDIR:=/home/ldw/core/linux-3.5
all:
make -C $(KDIR) M=$(PWD) modules
arm-linux-gcc app.c -o app -lpthread
clean:
rm -rf *.ko *.o *.mod.c *.order *.symvers

三、原子操作
3.1 什么叫做原子操作
比如在我们要对一个变量进行赋值的时候,在c语言里 int A=50,所有的C语言要转换成汇编语言
汇编语言进行赋值的时候 可能就是有好几个语句,可能一个赋值就要好几步
这几步在执行的过程中都有可能被打断,打断的后果就有可能造成赋值语句赋值的有问题
在linux系统里 所有的赋值语句都有一定的不安全性,比如我们写了一个简单的小功能,出现了这样的情况 无所谓;在重要领域上比如航天,假如赋值语句出现了问题 就会造成我们火箭偏离轨道
原子操作---在linux操作系统里 叫做不可分割操作
原子操作:就是赋值 这个赋值跟我们之前所学的赋值又有些不一样
我们的原子操作的赋值 是 不可被打断的,也就是说 我要用原子操作进行赋值,必然会赋值成功 不会被打断
3.2 原子操作相关的结构体
typedef struct {
int counter; //原子操作的对象
} atomic_t;
3.3 原子操作相关的API
① 设置原子的值:atomic_set(v, i)
参数:v:原子的结构体的指针
i:你要给原子赋的值
② 读取原子的值:int atomic_read(const atomic_t *v)
参数:const atomic_t *v:原子的核心结构体指针
③ 原子值进行加操作:atomic_add(int i, atomic_t * v)
参数:i:要给原子值加上的数值
v:原子的核心结构体指针
④ 原子值的减操作:atomic_sub(int i, atomic_t * v)
参数:i:要减去的值
v:原子的核心结构体指针
⑤ 原子的自加:atomic_inc(atomic_t * v)
⑥ 原子的自减:atomic_dec(atomic_t * v)
⑦ atomic_dec_and_test(atomic_t * v)
判断原子值是否为1:返回值:如果为1则返回真 否则返回0
/***********************************************************
文件名:led_atomic.c
功能:打印
***********************************************************/
#include<linux/kernel.h>
#include<linux/module.h>
#include<asm/types.h>
atomic_t myatomic;
static int __init myled_init(void)
{
atomic_set(&myatomic, 5);
printk("初始值myatomic=%d\n",atomic_read(&myatomic));
atomic_add(5, &myatomic);
printk("加后值myatomic=%d\n",atomic_read(&myatomic));
atomic_sub(2, &myatomic);
printk("减后值myatomic=%d\n",atomic_read(&myatomic));
atomic_inc(&myatomic);
printk("自加后值myatomic=%d\n",atomic_read(&myatomic));
atomic_dec(&myatomic);
printk("自减后值myatomic=%d\n",atomic_read(&myatomic));
if(atomic_dec_and_test(&myatomic))
{
printk("值为1\n");
}
else
{
printk("值不为1\n");
}
return 0;
}
static void __exit myled_exit(void)
{
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
obj-m += led_atomic.o //Makefile 文件
KDIR:=/home/ldw/core/linux-3.5
all:
make -C $(KDIR) M=$(PWD) modules
arm-linux-gcc app.c -o app -lpthread
clean:
rm -rf *.ko *.o *.mod.c *.order *.symvers
四:自旋锁
4.1 互斥锁
线程的互斥锁操作
① 初始化锁:pthread_mutex_init
② 上锁:pthread_mutex_lock
pthread_mutex_trylock
③ 解锁:pthread_mutex_unlock
④ 销毁锁:pthread_mutex_destroy
在内核里也有互斥锁
① 初始化锁:mutex_init
② 上锁:mutex_lock
mutex_trylock
③ 解锁:mutex_unlock
在内核里互斥锁的用法跟在多线程编程里使用的方法是一样的
4.2 自旋锁
自旋锁是嵌入里比较常用的一种底层锁,他有一个比较大的缺点就是占用的资源比较多,他就类似于while(1)会不停的询问条件是否能满足
自旋锁有一个比较好的地方,反应比互斥锁要及时
4.2.1 自旋锁相关的API
① 函数功能:初始化一个自旋锁的核心结构体
函数的头文件:linux/spinlock.h
函数的原型:spin_lock_init(spinlock_t *lock)
函数的参数:spinlock_t *lock:自旋锁的核心结构体的指针,需要声明一个这样的结构体
函数的返回值:无
② 函数的功能:阻塞的申请一个自旋锁
函数的头文件:linux/spinlock.h
函数的原型:void spin_lock(spinlock_t *lock)
函数的参数:spinlock_t *lock:自旋锁的核心结构体的指针
函数的返回值:无
③ 函数的功能:非阻塞的请求一个自旋锁
函数的头文件:linux/spinlock.h
函数的原型:int spin_trylock(spinlock_t *lock)
函数的参数:spinlock_t *lock:自旋锁的核心结构体的指针
函数的返回值:成功 返回0 失败 返回非零
④ 函数的功能:释放一个自旋锁
函数的头文件:linux/spinlock.h
函数的原型:void spin_unlock(spinlock_t *lock)
函数的参数:spinlock_t *lock:自旋锁的核心结构体的指针
函数的返回值:无
/***********************************************************
文件名:led_spin.c
功能:验证抢占CPU
***********************************************************/
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/workqueue.h>
#include<linux/timer.h>
#include<linux/delay.h>
#include<linux/spinlock.h>
spinlock_t mylock;
void myfunc1(struct work_struct *work)
{
int j=0;
//spin_lock(&mylock);
for(j=0;j<5;j++)
{
printk("func:%d\n",j);
ssleep(2);
}
//spin_unlock(&mylock);
printk("我是动态创建的小任务\n");
}
static int __init myled_init(void)
{
int i=0;
DECLARE_WORK(myqueue,myfunc1);
spin_lock_init(&mylock);
//1、初始化工作队列
schedule_work(&myqueue);
//spin_lock(&mylock);
for(i=0;i<5;i++)
{
printk("主程序:%d\n",i);
ssleep(2);
}
//spin_unlock(&mylock);
return 0;
}
static void __exit myled_exit(void)
{
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
obj-m += led_spin.o //Makefile 文件
KDIR:=/home/ldw/core/linux-3.5
all:
make -C $(KDIR) M=$(PWD) modules
arm-linux-gcc app.c -o app -lpthread
clean:
rm -rf *.ko *.o *.mod.c *.order *.symvers

/***********************************************************
文件名:led_spin.c
功能:验证抢占CPU
***********************************************************/
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/workqueue.h>
#include<linux/timer.h>
#include<linux/delay.h>
#include<linux/spinlock.h>
spinlock_t mylock;
void myfunc1(struct work_struct *work)
{
int j=0;
//spin_lock(&mylock);
for(j=0;j<20;j++)
{
printk("func:%d\n",j);
}
//spin_unlock(&mylock);
printk("我是动态创建的小任务\n");
}
static int __init myled_init(void)
{
int i=0;
DECLARE_WORK(myqueue,myfunc1);
spin_lock_init(&mylock);
//1、初始化工作队列
schedule_work(&myqueue);
//spin_lock(&mylock);
for(i=0;i<20;i++)
{
printk("主程序:%d\n",i);
}
//spin_unlock(&mylock);
return 0;
}
static void __exit myled_exit(void)
{
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
obj-m += led_spin.o //Makefile 文件
KDIR:=/home/ldw/core/linux-3.5
all:
make -C $(KDIR) M=$(PWD) modules
arm-linux-gcc app.c -o app -lpthread
clean:
rm -rf *.ko *.o *.mod.c *.order *.symvers

五、异步通知
5.1 什么叫做异步通知
异步通知就是一个信号,查看所有的信号:kill -l
异步通知的流程是:当文件出现了一个可读性的时候,内核会向应用层发送一个信号,这个信号就是 29号信号 SIGIO

5.2 内核层的异步通知函数
要想我们的设备文件支持异步通知,必须在内核里实现内核层异步通知的函数
函数的原型:int (*fasync) (int, struct file *, int);
要想实现内核层的异步通知的函数,只需要调用一个函数即可,
函数的原型:int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
函数的参数:前三个参数直接填写内核层异步通知函数的参数即可
struct fasync_struct **fapp:需要我们申请一个一级的指针,对这个一级指针进行取地址 放入这个函数里即可,当fasync实现完成之后 我们的驱动文件就支持了异步通知
怎样去向内核发送一个29号信号呢?
函数功能:专门去发送信号到应用层的一个函数
函数原型:void kill_fasync(struct fasync_struct **fp, int sig, int band)
函数参数:struct fasync_struct **fp:这个就是fasync_helper这个函数最后一个参数
int sig:信号 SIGIO 29
int band:当文件可读时 为POLL_IN
5.3 代码示例
/*********************************************************
文件名:key.c
功能:异步机制验证
*********************************************************/
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/miscdevice.h>
#include<linux/fs.h>
#include<linux/gpio.h>
#include<linux/irq.h>
#include<linux/interrupt.h>
#include<linux/poll.h>
#include<asm/uaccess.h>
#include<linux/signal.h>
struct fasync_struct * myfapp;
struct file_operations myfops;
struct miscdevice mymisc;
int irq_num;
int value;
irqreturn_t myfunc(int num, void *arg)
{
kill_fasync(&myfapp, SIGIO, POLL_IN);
return 0;
}
int myopen (struct inode *inode, struct file *file)
{
int ret;
//1:中断号转换
irq_num=gpio_to_irq(EXYNOS4_GPX3(2));
//2:使能中断号
enable_irq(irq_num);
//3:注册中断
ret=request_irq(irq_num, myfunc, IRQ_TYPE_EDGE_FALLING, "mykey", NULL);
return 0;
}
int myclose (struct inode *inode, struct file *file)
{
free_irq(irq_num, NULL);
disable_irq(irq_num);
return 0;
}
ssize_t myread (struct file *file, char __user *buf, size_t size, loff_t *oft)
{
int ret;
value=gpio_get_value(EXYNOS4_GPX3(2));
ret=copy_to_user(buf, (void *)&value, 1);
return 0;
}
int myfasync (int on, struct file * file, int nd)
{
fasync_helper(on, file, nd,&myfapp);
return 0;
}
static int __init mykey_init(void)
{
myfops.owner=THIS_MODULE;
myfops.open=myopen;
myfops.release=myclose;
myfops.read=myread;
myfops.fasync=myfasync;
mymisc.minor=255;
mymisc.name="key";
mymisc.fops=&myfops;
misc_register(&mymisc);
return 0;
}
static void __exit mykey_exit(void)
{
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
#include<stdio.h> //app_sync.c 文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
#include<signal.h>
int fd;
char buf[10];
void myread(int num)
{
read(fd,buf,1);
printf(" buf=%d\n",buf[0]);
}
int main()
{
int flag=0;
int ret=0;
fd=open("/dev/key",O_RDWR);
signal(SIGIO,myread);
fcntl(fd,F_SETOWN,getpid());
flag=fcntl(fd,F_GETFL);
fcntl(fd, F_SETFL,flag|FASYNC);
while(1)
{
sleep(2);
}
return 0;
}
obj-m += key.o //Makefile 文件
KDIR:=/home/ldw/core/linux-3.5
all:
make -C $(KDIR) M=$(PWD) modules
arm-linux-gcc app_sync.c -o app_sync
clean:
rm -rf *.ko *.o *.mod.c *.order *.symvers
可以看到,app_sync程序几乎不占用CPU,这里杀死这个进程,再重新执行这个应用程序,按下第一个按键,打印出buf=0。
2580

被折叠的 条评论
为什么被折叠?



