linux 内核 同步 思想,linux内核同步-1

应用程序的同步问题

进程间通信

我们知道,每个进程都有自己的独立地址空间;

进程与进程间的通信必须使用内核提供的方式才能进行;

应用编程的进程间通信有如下方法:

传统的进程间通信方式

无名管道(pipe)、有名管道(fifo)和信号(signal)

System V IPC对象

共享内存(share memory)、消息队列(message queue)和信号灯(semaphore)

BSD

套接字(socket)

共享内存是进程间最高效的通信方式,其原理如下图:

b37f30e61cd9

共享内存的应用程序必须特别留意保护共享资源,防止共享资源被并发访问。

共享资源之所以要防止并发访问,是因为如果多个任务(进程或线程)同时访问和操作数据,就有可能发生各任务之间相互覆盖共享数据的情况,造成被访问数据处于不一致状态。

并发访问共享数据是造成系统不稳定的一类隐患,而且这种错误一般难以跟踪和调试—所以首先应该认识到这个问题的重要性。

什么是临界区和竞争条件

临界区

访问和操作共享数据的代码段;

多个线程同时访问共享的资源会存在不安全性;

代码在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样。

竞争条件(bug)

两个线程同时进入同一个 临界区会发生

同步

保证不安全的并发不发生,竞争条件不发生

如何防止竞争条件?

禁止多个进程同时访问共享资源。

没有两个进程同时处于临界区.

不应对CPU的速度和数量作任何假设.

临界区之外的进程不能阻塞其他进程进入临界区.

不应该有进程永远等待进入临界区.

b37f30e61cd9

什么造成了并发?

中断----随时打断当前正在执行的代码;

Softirqs和tasklets ----内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在执行的代码;

内核抢占----内核中的任务可能会被另一任务抢占;

用户空间的睡眠和同步 ----在内核执行的进程可能会睡眠,这就会唤醒调度程序,从而导致调度一个新的用户进程执行

SMP----两个或多个处理器可以同时执行代码;

我们怎么知道什么东西需要保护?

不需要保护

执行线程的局部数据仅仅被它本身访问

如果数据只会被特定的进程访问

在写内核驱动的时候我们需要考虑以下的问题:

是否全局变量?

这些数据是进程上下文和中断上下文之间共享的数据吗?

如果一个进程在访问这些数据时被抢占了,新调度的进程可以访问这些数据吗?

当前进程是否可以睡眠在(阻塞)在一些资源上?

怎样防止数据失控?

这个函数如果在另一个处理器调用,会发生什么事情?

什么是死锁

举一个死锁的例子:

b37f30e61cd9

造成死锁的原因:

多线程多资源

每一个线程都在等待其中的一个资源

但所有的资源都已被占用

所有线程都在相互等待,但它们永远不会释放已经占有的资源。于是任何线程都无法继续,这便发生了死锁。

b37f30e61cd9

怎么防止死锁发生呢?

锁的顺序至关重要

防止饥饿

不要对同一个锁上两次锁

锁的方案过于复杂易导致死锁

原子操作

原子操作可以保证指令以原子的方式执行——执行过程不被打断。

原子原本指的是不可分割的微粒,所以原子操作也就是不能够被分割的指令。

内核提供了两组原子操作接口

一组针对整数进行操作;

另一组针对单独的位进行操作

原子整数操作:

原子整数方法使用一种特殊的类型: atomic_t

引入了一个特殊数据类型主要是出于一下原因:

首先,让原子函数只接受atomic_t类型的操作数,可以确保原子操作函数只与这种特殊类型数据一起使用。

同时,这也保证了该类型的数据不会被传递给其他任何非原子函数。

最后,在不同体系结构上实现原子操作的时候,使用atomic_t可以屏蔽其间的差异。

使用原子整型操作需要的声明都在文件中。

使用例子:

b37f30e61cd9

原子整数操作最常见的用途就是实现计数器。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值