问:linux的makefile中这句cpm_uart-objs := cpm_uart_core.o $(cpm_uart-objs-y)是什么意思?
答:cpm_uart-objs := cpm_uart_core.o $(cpm_uart-objs-y)就是设一个变量,你可以用$(cpm_uart-objs)使用它比如all:$(cpm_uart-objs)等于all:cpm_uart_core.o $(cpm_uart-objs-y)
adb shell getevent 命令可直接查看input上报的键值。
1、一般gpio_request封装了mem_request(),起保护作用,最后要调用mem_free之类的。主要是告诉内核这地址被占用了。当其它地方调用同一地址的gpio_request就会报告错误,该地址已被申请。在/proc/mem应该会有地址占用表描述。
这种用法的保护作用前提是大家都遵守先申请再访问,有一个地方没遵守这个规则,这功能就失效了。好比进程互斥,必需大家在访问临界资源的时候都得先获取锁一样,其中一个没遵守约定,代码就废了。
2、__gpio_set_value和gpio_set_value的区别
一般带__这种操作的宏和函数是未保护的,对这中__操作的使用最好不用,除非你知道其中的原理。
你说的这种显然就是地址检测保护了。主要是防止错误地址引用。__gpio_set_vallue是没有地址范围检测的,如果引用非法地址,有可能内核down掉。
.remove = __devexit_p(oem_led_remove),
__devexit_p的作用是如果驱动不是以模块的形式加载进内核,则不使用remove函数。
linux系统增加环境变量
在~/.bashrc文件末尾增加:
export PATH=$PATH:/yourdir1 /yourdir2
然后:source .bashrc
驱动程序编写时通常需要用到的内核机制主要有:定时器,内存分配,中断申请,并发机制(自旋锁,互斥体,信号量,原子操作等),底半部(有softirq,tasklet,work queue),内核线程,I/O端口操作
定时器(长延时):定时器也是中断的一种
HZ(每秒钟的系统定时器节拍数,2.6内核中默认为1000,其值越大,定时器间隔时间就越小,进程调度准确性就越高,但会导致开销更大)
jiffies:记录了系统启动以来系统定时器已经触发的次数
time_after()
time_before()
time_after_eq()
time_before_eq()
忙等待:即进程在一直在原地等待,且不放弃CPU,可以通过time_before来实现,但忙等待对系统资源消耗太大,不建议使用。可应用于中断上下文中,但不能长时间等待。
睡眠等待:在等待时会让出CPU给其它进程使用,特别要注意的是,睡眠等待不能应用于中断上下文中,常用函数如下:
schedule_timeout(HZ)
wait_event_timeout() //同下面的msleep都是基于schedule_timeout实现的
msleep() //表示睡眠指定时间
驱动中使用定时器功能的方法:
头文件:#include <linux/time.h>
struct timer_list my_timer;
init_timer(my_timer);
my_timer.expire = jiffers + n*HZ;
my_timer.function=timer_func;
my_timer.data=func_parameter;
add_timer(my_timer);
mod_timer() //修改到期时间
del_timer()
timer_pending() //查看当前是否处于等待状态
clock_settime() / clock_gettime() 用于用户空间获得定时器服务
短延时:内核中。小于jiffy的延时被认为是短延时,所以不能使用基于jiffy的方法来实现延时,又所以上述睡眠等待不能用于短延时。唯一可以实现短延时的方法就是忙等待,故短延时不能睡眠。内核中实现短延时的方法有:mdelay(),udelay(),ndelay()
mdelay的实现原理:
测量处理器执行一条指令的时间,内核会在启动过程中进行测量,并将结果保存在loops_per_jiffy变量中。
内存分配:
kmalloc GFP_KERNEL(进程上下文中使用,如果内存资源不足分配不成功能进入睡眠) GFP_ATOMIC(中断上下文中使用,不能睡眠,所以分配成功率比GFP_KERNEL要低) GFP:get free page
kzalloc 分配内存空间的同时给这段内存空间赋值为0
vmalloc 可以分配不连续的内存空间,适合比较大的内存申请
自旋锁:自旋锁使程序进入临界区,自旋锁从打开到释放中间不能被打断,否则系统很大可能会死锁,所以中断处理函数中也可以使用自旋锁,但是必须
使用包含禁止中断的自旋锁spin_lock_irqsave / spin_unlock_irqrestore
自旋锁通用函数:spin_lock / spin_unlock
也可以设置自旋锁只禁止软件中断(比如:tasklet),而硬件中断正常工作,其函数为:spin_lock_bh / spin_unlock_bh
自旋锁初始化:
可以在函数编译时初始化:spinlock_t mylock = SPIN_LOCK_UNLOCKED
代码运行时初始化:spin_lock_init()
互斥体:
互斥体与自旋锁最大区别是进程可以进入睡眠,所以它不能用于中断处理程序中。正是因为它能让进程进入睡眠,所以它可以用在临界区需要处理时间比较长的地方。而自旋锁要尽可能的短。
DEFINE_MUTEX() //静态定义一个互斥体
mutex_init() //动态定义一个互斥体
mutex_lock()
mutex_unlock()
信号量:信号量是在互斥体出现之前老的并发机制,但目前仍然经常被使用
DECLARE_MUTEX() //静态定义一个信号量
init_MUTEX() //动态定义一个信号量
up()
down()
其它互斥机制:除了上述互斥机制外,内核还支持另外一些机制,它们很多是上述机制的变体,在一些场合使用这些机制,效果会更好
原子变量:
atomic_inc()
atomic_inc_and_test()
atomic_dev()
atomic_dec_and_test()
clear_bit()
set_bit()
test_bit()
test_and_set_bit()
读写锁:
自旋锁的读写变体
read_lock()
read_unlock()
read_lock_irqsave()
read_lock_irqrestore()
write_lock()
write_unlock()
write_lock_irqsave()
write_lock_irqrestore()
信号量的读写变体
up_read() / down_read()
up_write() / down_write()
seqlock操作
read_seqbegin() / read_seqretry()
write_seqlock() / write_sequnlock()
禁止中断
local_irq_disable() //禁止本CPU上中断
local_irq_enable()
local_irq_save() //先保存中断状态并禁止中断
local_irq_restore()
中断:
request_irq(irq,handle,...) / free_irq()
中断号可以自己定义一个具体数值,也可以根据平台给定的申请函数动态申请中断号,中断一般不在模块初始化的时候定义,因为中断资源宝贵,所以中断一般在第一次使用时注册,在最后一次使用后释放。
由于中断上下文中不能让出CPU,如果中断执行过程较长将会严重影响系统的实时性,这种情况下,可以使用系统的另一机制解决。
把中断处理程序分为两个部分:上半部和下半部(也有称顶半部,底半部),上半部急切抢占并与硬件交互,下半部处理所有使能的中断,不急切。
底半部有三种实现机制:softirq,tasklet和work queue
softirq:是基本的底半部机制,有较强加锁需求,仅仅在一些性能敏感的子系统中才会使用softirq。
tasklet:建在softirq之上,使用更简单,除有严格的可扩展性和速度要求,都建议用tasklet。更softirq最主要的区别是,softirq可重用,而tasklet不可重用。softirq的不同实例可运行于不同的处理器上,而tasklet则不允许。
softirq和tasklet跟中断一样,都不能睡眠。
工作队列:如果底半部需要睡眠,则必须使用工作队列。
IO端口分配:
request_region() / release_region() / check_region() //因为IO口通常会被多个驱动程序使用,所以使用IO资源时要进行注册,并且要及时释放。
使用IO端口:inb / outb(8位读写) inw / outw(16位读写) inl / outl(32位读写)
IO内存:除了x86上普遍使用的IO端口之外,和设备通信的另一种主要机制是通过使用映射到内存的寄存器或设备内存。这两种都称为IO内存。
IO内存的分配和使用:
request_mem_region() / release_mem_region() / check_mem_region()
ioremap() / iounmap()
写驱动时要注意以下函数的应用,功能是便于驱动程序传递指针:
platform_set_drvdata(pdev, ip); / platform_get_drvdata()
input_set_drvdata(input_dev, ip); / input_get_drvdata()
i2c_set_clientdata() / i2c_get_clientdata()
dev_set_drvdata / dev_get_drvdata 以上函数都是通过调用这组函数实现的,在内核源码中基本上每个子系统都有一个这样的函数,再此不一一列举。
在写platform形式的驱动程序时,至少需要构造三个结构体:
1.在platform device那边需要定义一个结构体存放需要传递给platform driver那边的与硬件平台相关的资源,比如:中断号,GPIO等资源
2.在platform driver这边需要定义一个大的结构体,并在其中嵌套定义platform device的结构体 和 子系统的结构体,比如:input设备需要嵌套struct input_dev,i2c设备还有嵌套struct i2c_client,总之,通过这个结构体可以调用一切与所在驱动相关的资源。
3.第三个结构体,即子系统结构体,这个系统已经构造好了。比如:i2c子系统要使用struct i2c_client,input子系统要使用struct input_dev
以一个驱动名为ft5x06的tp驱动为例:
struct ft5x06 {
u16 chip;
struct input_dev *input;
struct i2c_client *client;
struct ft5x06_platform_data *pdata;
spinlock_t lock;
int irq;
int use_mt; /* if support multi-Touch */
struct work_struct work;
u16 tp_x_res; /* touch panel x resolution */
u16 tp_y_res; /* touch panel y resolution */
u8 Step;
#ifdef CONFIG_HAS_EARLYSUSPEND
struct early_suspend early_suspend;
#endif
u16 lcd_x_res; /* LCD x resolution */
u16 lcd_y_res; /* LCD y resolution */
};
struct ft5x06_platform_data {
int use_mt;
u16 tp_x_res;
u16 tp_y_res;
int (*suspend)(void);
int (*resume)(void);
int (*io_init)(void); /* call when probe start */
int (*power)(int on); /* call after io_init */
int (*chip_init)(void); /* call after power on */
int (*io_deinit)(void); /* call when probe fail */
};
注意:struct ft5x06_platform_data这个结构体定义在/include/linux/input/目录中,方便调用。
to_platform_device(dev) //通过device找到platform_device
file_operations中的write函数在实现时要注意,其第二个参数为“ const char __user *buf”,不能漏了const关键字,否则严格编译时会报错。
strict_strtoul //内核中将字符型转换成unsigned long型数据。