12. 驱动初步-------内核同步机制

一、内核的同步机制

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值