应用程序的同步问题
进程间通信
我们知道,每个进程都有自己的独立地址空间;
进程与进程间的通信必须使用内核提供的方式才能进行;
应用编程的进程间通信有如下方法:
传统的进程间通信方式
无名管道(pipe)、有名管道(fifo)和信号(signal)
System V IPC对象
共享内存(share memory)、消息队列(message queue)和信号灯(semaphore)
BSD
套接字(socket)
共享内存是进程间最高效的通信方式,其原理如下图:
共享内存的应用程序必须特别留意保护共享资源,防止共享资源被并发访问。
共享资源之所以要防止并发访问,是因为如果多个任务(进程或线程)同时访问和操作数据,就有可能发生各任务之间相互覆盖共享数据的情况,造成被访问数据处于不一致状态。
并发访问共享数据是造成系统不稳定的一类隐患,而且这种错误一般难以跟踪和调试—所以首先应该认识到这个问题的重要性。
什么是临界区和竞争条件
临界区
访问和操作共享数据的代码段;
多个线程同时访问共享的资源会存在不安全性;
代码在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样。
竞争条件(bug)
两个线程同时进入同一个 临界区会发生
同步
保证不安全的并发不发生,竞争条件不发生
如何防止竞争条件?
禁止多个进程同时访问共享资源。
没有两个进程同时处于临界区.
不应对CPU的速度和数量作任何假设.
临界区之外的进程不能阻塞其他进程进入临界区.
不应该有进程永远等待进入临界区.
什么造成了并发?
中断----随时打断当前正在执行的代码;
Softirqs和tasklets ----内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在执行的代码;
内核抢占----内核中的任务可能会被另一任务抢占;
用户空间的睡眠和同步 ----在内核执行的进程可能会睡眠,这就会唤醒调度程序,从而导致调度一个新的用户进程执行
SMP----两个或多个处理器可以同时执行代码;
我们怎么知道什么东西需要保护?
不需要保护
执行线程的局部数据仅仅被它本身访问
如果数据只会被特定的进程访问
在写内核驱动的时候我们需要考虑以下的问题:
是否全局变量?
这些数据是进程上下文和中断上下文之间共享的数据吗?
如果一个进程在访问这些数据时被抢占了,新调度的进程可以访问这些数据吗?
当前进程是否可以睡眠在(阻塞)在一些资源上?
怎样防止数据失控?
这个函数如果在另一个处理器调用,会发生什么事情?
什么是死锁
举一个死锁的例子:
造成死锁的原因:
多线程多资源
每一个线程都在等待其中的一个资源
但所有的资源都已被占用
所有线程都在相互等待,但它们永远不会释放已经占有的资源。于是任何线程都无法继续,这便发生了死锁。
怎么防止死锁发生呢?
锁的顺序至关重要
防止饥饿
不要对同一个锁上两次锁
锁的方案过于复杂易导致死锁
原子操作
原子操作可以保证指令以原子的方式执行——执行过程不被打断。
原子原本指的是不可分割的微粒,所以原子操作也就是不能够被分割的指令。
内核提供了两组原子操作接口
一组针对整数进行操作;
另一组针对单独的位进行操作
原子整数操作:
原子整数方法使用一种特殊的类型: atomic_t
引入了一个特殊数据类型主要是出于一下原因:
首先,让原子函数只接受atomic_t类型的操作数,可以确保原子操作函数只与这种特殊类型数据一起使用。
同时,这也保证了该类型的数据不会被传递给其他任何非原子函数。
最后,在不同体系结构上实现原子操作的时候,使用atomic_t可以屏蔽其间的差异。
使用原子整型操作需要的声明都在文件中。
使用例子:
原子整数操作最常见的用途就是实现计数器。
atomic_inc(atomic_t *v) 自加1
atomic_dec(atomic_t *v) 自减1
还可以用原子整数操作原子地执行一个操作并检查结果。一个常见的例子就是原子的减操作和检查。
int atomic_dec_and_test(atomic_t *v);
这个函数将给定的原子变量减1,如果结果为0,就返回真;否则返回假。
原子操作函数如下:
ATOMIC_INlT(int i); 在声明一个atomic_t变量时,将它初始化为i
int atomic_read(atomic_t *v); 原子地读取整数变量v
void atomic_set(atomic_t *v, int i); 原子地设置v值为1
void atomtic_add(int i,atomic_t *v); 原子地给v加i
void atomic_sub(int i,atomic_t *v); 原子地从v减i
void atomic_inc(atomic_t *v); 原子地给v加1
void atomic_dec(atomic_t *v); 原子地从v减1
int atomic_sub_and_test(int i, atomic_t *v);原子地从v减i,如果结果等于0返回真;否则返回假
int atomtic_add_negative(int i,atomic_t *v); 原子地给v加i,如果结果是负数,返回真;否则返回假
int atomic_dec_and_test(atomic_t *v); 原子地给v减1,如果结果是0,返回真;否则返回假
int atomic_inc_and_test(atomic_t *v); 原子地给v加1,如果结果是0,返回真;否则返回假
原子位操作
原子位操作函数的头文件:
原子位操作函数是对普通的内存地址进行操作的,没有特定的数据类型。
原子位操作函数的参数是一个指针和一个位号
第0位是给定地址的最低有效位;
在32位机上,第31位是给定地址的最高有效位;
而第32位是下一个字的最低有效位
使用原子整数操作的例子:
unsigned long word = 0;
set_bit(0, &word); /* 第0位被设置为1(原子操作) */
set_bit(1, &word); /*第1位被设置为1(原子操作) */
printk(“%ul\n”, word); /* 将会打印 "3" */
clear_bit(1, &word); /* 第1位被清零 */
change_bit(0, &word); /* 第0位被反转,即被清零(原子操作) */
/*设置第0位,并返回以前的值(0)*/
if (test_and_set_bit(0, &word)) {
/* 永远不会为真*/
}
/* 下面的语句是合法的*/
word = 7;
void set_bit(int nr,void *addr) 原子地设置addr所指对象的第nr位
void clear_bit(int nr,void*addr) 原子地清空addr所指对象的第nr位
void change_bit(int nr,void *addr) 原子地翻转addr所指对象的第nr位
int test_and_set_bit(int nr,void*addr) 原子地设置addr所指对象的第nr位,并返回原先的值
int test_and_clear_bit(int nr,void *addr) 原子地清空addr所指对象的第nr位,并返回原先的值
int lest_and_change_bit(int nr,void *addr) 原子地翻转addr所指对象的第nr位,并返回原先的值
int test_bit(int nr,void *addr) 原子地返回addr所指对象的第nr位
为方便起见,内核还提供了一组与上述操作对应的非原子位函数。
非原子位函数与原子位函数的操作完全相同,但是,前者不保证原子性,且其名字前缀多两个下划线。例如,与test_bit()对应的非原子形式是__test_bit()。
如果你不需要原子性操作(比如说,如果你已经用锁保护了自己的数据),那么这些非原子的位函数相比原子的位函数可能会执行得更快些。
内核还提供了两个例程用来从指定的地址开始搜索第一个被设置(或未被设置)的位。
int find_first_bit(unsigned long*addr,unsigned int size)
int find_first_zero_bit(unsigned long*addr,unsigned int size)
这两个函数中第一个参数是一个指针
第二个参数是要搜索的总位数
返回值分别是第一个被设置的(或没被设置的)位的位号
如果你的搜索范围仅限于一个字,使用__ffs()和__ffz()这两函数更好,它们只需要给定一个要搜索的地址做参数。
原子操作实战
驱动代码如下,以点亮led为例子:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static unsigned long gpm4con;
#define GPM4CON (*(volatile unsigned long *)(gpm4con + 0x0))
#define GPM4DAT (*(volatile unsigned long *)(gpm4con + 0x04))
static atomic_t flag = ATOMIC_INIT(1);
//open
ssize_t led_open(struct inode *inop,struct file *filp)
{
/* if(atomic_dec_and_test(&flag) == 0)
{
atomic_inc(&flag);
return -EBUSY;
}
*/
if(atomic_read(&flag))
atomic_dec(&flag);
else
{
printk("EBUSY = %d\r\n",EBUSY);
return -EBUSY;
}
GPM4CON = 0X1111;
GPM4DAT &= ~0XF;
printk("led open!\n");
return 0;
}
//release
ssize_t led_release(struct inode *inop,struct file *filp)
{
atomic_inc(&flag);
GPM4DAT |= 0xf;
printk("led release!\n");
return 0;
}
static unsigned char led_state = 0;
//read
ssize_t led_read(struct file *filp,char __user *buf,size_t size,loff_t f_pos)
{
copy_to_user(buf,&led_state,size);
printk("led read data %d\n",led_state);
return size;
}
//write
ssize_t led_write(struct file *filp,const char __user *buf,size_t size,loff_t f_pos)
{
copy_from_user(&led_state,buf,size);
printk("led write data %d\n",led_state);
GPM4DAT=led_state;
return size;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
static unsigned int led_major = 0;
static struct class *led_class;
#define LED_DRV_NAME "led_drv"
static int __init led_init(void)
{
gpm4con=(unsigned int)ioremap(0x110002e0,12);
led_major=register_chrdev(0,LED_DRV_NAME,&fops);
led_class=class_create(THIS_MODULE,LED_DRV_NAME);
device_create(led_class,NULL,MKDEV(led_major,0),NULL,LED_DRV_NAME);
return 0;
}
static void __exit led_exit(void)
{
device_destroy(led_class,MKDEV(led_major,0));
class_destroy(led_class);
iounmap(gpm4con);
unregister_chrdev(led_major,LED_DRV_NAME);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
分别写两个测试程序来测试:
led1.c代码如下:
#include
#include
#include
#include
#include
#define FILE_NAME "/dev/led_drv"
int main(void)
{
int fd = 0;
unsigned char val;
int i = 0,j=0;
printf("我是进程1!\r\n");
do{
fd = open(FILE_NAME,O_RDWR);
printf("进程1 fd = %d\n",fd);
sleep(1);
}while(fd < 0);
sleep(2);
printf("led1 open ok!\r\n");
//left
val = 0xfe;
for(i=0;i<3;i++)
{
for(j=0;j<4;j++)
{
printf("led1\n");
usleep(1); //应用层输出切换内核输出,加一点延时,否则会执行乱序
write(fd,&val,1);
val = val<<1 | val>>7;
usleep(200000);
}
}
close(fd);
printf("led1 exit\n");
return 0;
}
led2.c
#include
#include
#include
#include
#include
#define FILE_NAME "/dev/led_drv"
int main(void)
{
int fd = 0;
unsigned char val;
int i = 0,j=0;
printf("我是进程2!\r\n");
do{
fd = open(FILE_NAME,O_RDWR);
printf("进程2 fd = %d\n",fd);
sleep(1);
}while(fd < 0);
sleep(1);
printf("led2 open ok!\r\n");
//right
val = 0xf7;
for(i=0;i<3;i++)
{
for(j=0;j<4;j++)
{
printf("led2\n");
usleep(10);
write(fd,&val,1);
val = val>>1 | val<<7;
usleep(100000);
}
}
close(fd);
printf("led2 exit\n");
return 0;
}
makefile如下:
obj-m += led_drv.o
all:
make -C /home/sice/linux-3.5 M=`pwd` modules
install:
make -C /home/sice/linux-3.5 M=`pwd` INSTALL_MOD_PATH=/opt/rootfs modules_install
clean:
make -C /home/sice/linux-3.5 M=`pwd` clean
rm -rf led1 led2
led:
arm-linux-gcc led1.c -o led1
arm-linux-gcc led2.c -o led2
copy:
cp led1 led2 /opt/rootfs
cp led.sh /opt/rootfs