嵌入式培训专家
LinuxLinux 设备驱动开发设备驱动开发
www.farsight.com.cn
华清远见 今天的内容
Linux设备驱动的现状
从 nonos驱动到 Linux驱动
内核设施
自旋锁、信号量、互斥量、完成量
异步
通知
关于发布提成方案的通知关于xx通知关于成立公司筹建组的通知关于红头文件的使用公开通知关于计发全勤奖的通知
、信号
阻塞与非阻塞
内存与 I/O操作, DMA
中断, top half/bottom half
字符设备驱动
复杂设备驱动的框架
LCD设备 FRAMEBUFFER
FLASH设备MTD
TTY设备
块设备
用户空间的设备驱动
设备驱动开发流程
开发环境建设
调试手段
用户空间测试
设备驱动的学习方法
华清远见 Linux设备驱动的现状
高需求
Linux内核的绝大多数代码为设备驱动
新设备、新芯片、新驱动的需求
高门槛
涉及到大量硬件操作
涉及到内核基础知识
涉及到并发控制与同步
复杂的软件结构框架
高回报
华清远见 从 nonos驱动到 Linux驱动
nonos驱动
单刀直入 简单 直接提供 API
Linux驱动
兵团战役 复杂 间接提供 API
应用软件
SerialSend
SerialRecv
设备驱动
LightOn
LightOff
FlashWr
FlashRd
硬件
串口 LED FLASH
硬件
操作系统 API
操作系统
驱动中独立于设备的接口
驱动中的硬件操作
用户应用程序
nonos驱动与应用 onos驱动与应用
华清远见 并发和竞态
并发和竞态:
对称多处理器 ( SMP)的多个 CPU
单 CPU内进程与抢占它的进程
中断(硬中断、软中断、 Tasklet、底半部)与进程之间
处理思路:
lock() //锁定,拿虎符
. . .
critical section //临界区,调动军队
. . .
unlock() //解锁定,归还虎符
常用方法 :
中断屏蔽
原子操作
自旋锁
信号量
互斥体
华清远见 原子变量
接口
• 整型原子操作
•
� 设置原子变量的值
• void atomic_set(atomic_t *v, int i); //设置原子变量的值为 i
• atomic_t v = ATOMIC_INIT(0); //定义原子变量 v并初始化为 0
•
获取原子变量的值
• atomic_read(atomic_t *v); //返回原子变量的值
•
� 原子变量加 /减
• void atomic_add(int i, atomic_t *v); //原子变量增加 i
• void atomic_sub(int i, atomic_t *v); //原子变量减少 i
• ξ 原子变量自增 /自减
• void atomic_inc(atomic_t *v); // 原子变量增加 1
• void atomic_dec(atomic_t *v); // 原子变量减少 1
• η 操作并测试
• int atomic_inc_and_test(atomic_t *v);
• int atomic_dec_and_test(atomic_t *v);
• int atomic_sub_and_test(int i, atomic_t *v);
• 操作并返回
• int atomic_add_return(int i, atomic_t *v);
• int atomic_sub_return(int i, atomic_t *v);
• int atomic_inc_return(atomic_t *v);
• int atomic_dec_return(atomic_t *v);
• 位原子操作
• 设置 /清除 /反转位
• void set_bit(nr, void *addr);
• void clear_bit(nr, void *addr);
• void change_bit(nr, void *addr);
•
� 测试位
• test_bit(nr, void *addr);
• η 测试并操作位
• int test_and_set_bit(nr, void *addr);
• int test_and_clear_bit(nr, void *addr);
• int test_and_change_bit(nr, void *addr);
华清远见 自旋锁 VS 信号量
自旋锁:
忙等待,无调度开销
进程抢占被禁止
锁定期间不能睡觉
• spinlock_t lock;
• spin_lock_init(&lock);
• spin_lock (&lock) ; //获取自旋锁,保护临界区
• . . ./ /临界区
• spin_unlock (&lock) ; // 解锁
信号量
拿不到就切换进程,有调度开销
锁定期间可以睡觉,不用于中断上下文
• //定义信号量
• DECLARE_MUTEX(mount_sem);
• down(&mount_sem);//获取信号量,保护临界区
• . . .
• critical section //临界区
• . . .
• up(&mount_sem);//释放信号量
华清远见 设备访问方式
读写函数
读写
读写系统
调用
资源不
可获得
资源可
获得
阻塞
阻塞I/O
返回
返回 用户空间
poll()或select()
poll系统调用
资源状态变更
非阻塞轮询 I/O
返回
xxx_func()
唤醒
读写
yyy_func() 中断
唤醒
资源可获得
资源状态变更
唤醒
读写函数
阻塞 读写系统
调用
返回
返回
读写函数
读写系统调用
资源可
获得
signal
信号处理函数(进行读写)
异步通知
系统调用
内核空间中断
signal
华清远见 阻塞非阻塞
等待队列:进程等待被唤醒的一种机制
阻塞与非阻塞使用模板
1 static ssize_t xxx_write(struct file *file, const char *buffer, size_t count,
2 loff_t *ppos)
3 {
4 ...
5 DECLARE_WAITQUEUE(wait, current); //定义等待队列
6 add_wait_queue(&xxx_wait, &wait); //添加等待队列
7
8 ret = count;
9 /* 等待设备缓冲区可写 */
10 do
11 {
12 avail = device_writable(...);
13 if (avail < 0)
14 __set_current_state(TASK_INTERRUPTIBLE);//改变进程状态
15
16 if (avail < 0)
17 {
18 if (file>f_flags &O_NONBLOCK) //非阻塞
19 {
20 if (!ret)
21 ret = EAGAIN;
22 goto out;
23 }
24 schedule(); //调度其他进程执行
25 if (signal_pending(current))//如果是因为信号唤醒
26 {
27 if (!ret)
28 ret = ERESTARTSYS;
29 goto out;
30 }
31 }
32 }while (avail < 0);
33
34 /* 写设备缓冲区 */
35 device_write(...)
36 out:
37 remove_wait_queue(&xxx_wait, &wait);//将等待队列移出等待队列头
38 set_current_state(TASK_RUNNING);//设置进程状态为 TASK_RUNNING
39 return ret;
40 }
华清远见 polling
驱动中 POLL模板
• 1 static unsigned int xxx_poll(struct file *filp, poll_table *wait)
• 2 {
• 3 unsigned int mask = 0;
• 4 struct xxx_dev *dev = filp>private_data; /*获得设备结构体指针 */
• 6 ...
• 8 poll_wait(filp, &dev>wait, wait);
• 9
• 10 if (...)//可读
• 11 {
• 12 mask |= POLLIN | POLLRDNORM; /*标示数据可获得 */
• 13 }
• 15 if (...)//可写
• 16 {
• 17 mask |= POLLOUT | POLLWRNORM; /*标示数据可写入 */
• 18 }
• 19
• 20 ...
• 21 return mask;
• 22 }
用户空间 POLL模板
• fd_set fds;
• FD_ZERO(&fds);
• FD_SET(fd, &fds);
• select(fd + 1, &rfds, &wfds, NULL, NULL);
• if (FD_ISSET(fd, &fds))
• {
• printf("Poll monitor:can be access\n");
• }
华清远见 异步 I/O
信号 :软件意义上的“中断”
驱动发出信号
• kill_fasync(&dev>async_queue, SIGIO, POLL_IN);
用户空间应用程序处理信号
• 24 signal(SIGIO, input_handler);
• 25 fcntl(STDIN_FILENO, F_SETOWN, getpid());
• 26 oflags = fcntl(STDIN_FILENO, F_GETFL);
• 27 fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);
• 8 void input_handler(int num)
• 9 { ...
• 14 len = read(STDIN_FILENO, &data, MAX_LEN);
• 15 ... }
f c n t l( fd, F _S E T OWN, ge t p i d( ) ) f c n t l( f d , F _GE T FL)
信号处理函数
信号
signal()绑定
内核设置 filp>f_owner 设备驱动 fasync()函数 资源可获得
释放
导致
执行
内核空间
用户空间
华清远见 中断
两个半部
调
度
上半部
中断
(紧急的硬件操作 )
下半部
(延缓的耗时操作 )
机制
tasklet
工作队列
• 1 /*定义 tasklet和底半部函数并关联 */
• 2 void xxx_do_tasklet(unsigned long);
• 3 DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);4
• 5 /*中断处理底半部 */
• 6 void xxx_do_tasklet(unsigned long)
• 7 {...
• 9 }
• 11 /*中断处理顶半部 */
• 12 irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
• 13 { ...
• 15 tasklet_schedule(&xxx_tasklet);
• 16 ...}
华清远见 内存与 I/ O访问
内存空间与 I/ O空间
Linux内核地址空间
0
896M
4G
物理内存
3G
4G
Linux内核空间
物理内存映射区
vmalloc分配器区
高端内存映射区
专用页面映射区
保留区
内存申请
kmalloc,get_free_pages:物理连续,线性映射
vmalloc:物理非连续,非线性映射
物理/虚拟地址映射
静态映射
ioremap, ioremap_nocache
mmap:映射到用户空间
华清远见 DMA
cache一致性问题
cache对象 DMA缓冲区
DMA缓冲区
一致性缓冲区
• void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);
• void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);
流式 DMA映射
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);
void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,enum dma_data_direction
direction);
内存地址 /总线地址
华清远见 字符设备驱动
结构
file_operations
• 1 struct file_operations xxx_fops =
• 2 {
• 3 .owner = THIS_MODULE,
• 4 .read = xxx_read,
• 5 .write = xxx_write,
• 6 .ioctl = xxx_ioctl,
• 7 ...
• 8 };
华清远见 复杂设备驱动
复杂设备驱动的 framework
层次化
结构化
上层不依赖于具体硬件,下层与硬件接口
驱动核心层
硬件操作层
Linux上层
字符设备,块设备
file_operations
xxx设备
xxx_ops
华清远见 Framebuffer
应用程序
LCD控制器
用户空间
内核空间
硬件
f b_ r e a d( ) fb_ wr i t e( ) fb _mma p ( ) fb_ i o c t l( ) fbmem.c
file_operations
xxxfb.c
fb _ i n for e g i s t e r_ f ra me bu f f e r( ) 注册 un re g i s t e r_ f r a me bu f fe r( )注销
va r f i x fb_op s
fb _c h e c k_va r( ) f b_ s e t_p a r( )
从硬件无关到硬件相关:
file_operations>fb_info>fb_ops
从注册 cdev到注册 framebuffer
华清远见 MTD
MTD
nand core,主要
是nand_base.c
nand chip
mtd_info
nand_chip
层次结构
字符/块设备 >mtd_info>nand_chip
华清远见 TTY设备驱动
层次结构与数据流向
fs/char_dev.c
tty_io.c
/dev/ttyS0
xxx_tty.c n_tty.c
注册字符设备
struct file_operations
tty_register_ldisc()
struct tty_disc
tty_register_driver()
struct tty_driver
write() read()
用户空间
tty_write() tty_read()
tty核心
ldisc.write() ldisc.read()
线路规程
tty缓冲区 ldisc.receive_buf()
driver.write() tty_flip_buffer_push()
tty驱动
flip_buffer
中断处理函数
硬件层
数据流 函数调用
xxx_uart.c
fs/char_dev.c
tty_io.c
/dev/ttyS0
serial_core.c n_tty.c
注册字符设备
struct file_operations
tty_register_ldisc()
struct tty_disc
tty_register_driver()
struct tty_driver
u a r t_r eg i s t e r_ d r i v e r()
s t r u c t u a r t_ o p s
tty>uart
华清远见 块设备驱动
数据结构
block_device_operations
gendisk
request与 bio:表征等待进行的 I/O请求
I/O请求
请求队列
e l v_ n ex t_ r e q u e s t( )
r q_ f o r_ e a ch_b i o( )
b i o_ f o r_e a ch_ s e gmen t( )
华清远见 用户空间的设备驱动
从用户空间访问内存和 I/ O
userspace接口
User Mode SCSI
User Mode USB
User Mode I2C
UIO:drivers/uio/
华清远见 开发环境建设
服务器
Linux
gdb、gcc、
samba、 sshd
Windows、
ssh
Linux
mount nfs
nfs:目标板应用
程序
使用
存放
串口
Windows、
ssh
Linux
Linux
华清远见 驱动调试
printk()
/proc
oops
监视工具
kcore
kdb
kgdb
仿真器
华清远见 /printk
• 最简单,最常用的方法
调整打印级别: # echo 5 > /proc/sys/kernel/printk
使用宏:通过 make menuconfig选择是否包含打印信息
• #ifdef CONFIG_XXXDEBUG
• #xxx_debug(fmt,arg...) printk(KERN_DEBUG fmt,##arg)
• #else
• #xxx_debug(fmt,arg...)
• #endif
华清远见 /proc
从用户空间获取内核信息的方法
• 7 ssize_t simple_proc_read(char *page, char **start, off_t off, int count,
• 8 int*eof, void *data)
• 23 ssize_t simple_proc_write(struct file *filp, const char __user *buff, unsigned
• 24 long len, void *data)
• 55 int __init simple_proc_init(void)
• 56 {
• 57 proc_entry = create_proc_entry("sim_proc", 0666, NULL); //创建 /proc
• 58 if (proc_entry == NULL)
• 59 {...
• 62 }
• 63 else
• 64 {
• 65 proc_entry>read_proc = simple_proc_read;
• 66 proc_entry>write_proc = simple_proc_write;
• 67 proc_entry>owner = THIS_MODULE;
• 68 }
• 72 ...}
使用方法: cat, echo
华清远见 oops
• 8 static ssize_t oopsexam_write(struct file
*filp, const char *buf, size_t len, loff_t
• 9 *off)
• 10 {
• 11 int *p=0;
• 12 *p = 1; //故意访问 0地址
• 13 return len;
• 14 }
• Unable to handle kernel NULL pointer dereference at virtual address 00000000
• printing eip:
• c381a013
• *pde = 00000000
• Oops: 0002 [#1]
• PREEMPT SMP
• Modules linked in: oops_example
• CPU: 0
• EIP: 0060:[
] Not tainted VLI
• EFLAGS: 00010286 (2.6.15.5)
• EIP is at oopsexam_write+0x4/0x11 [oops_example]
• eax: 00000002 ebx: c2b35480 ecx: 00000000 edx: c381a00f
• esi: 00000002 edi: 080e9408 ebp: c2007fa4 esp: c2007f68
• ds: 007b es: 007b ss: 0068
• Process bash (pid: 2453, threadinfo=c2006000 task=c2021570)
• Stack: c015e036 c2b35480 080e9408 00000002 c2007fa4 00000000 c2b35480
fffffff7
• 080e9408 c2006000 c015e1d1 c2b35480 080e9408 00000002 c2007fa4
00000000
• 00000000 00000000 00000001 00000002 c0102f9f 00000001 080e9408
00000002
• Call Trace:
• [] vfs_write+0xc5/0x18f
• [] sys_write+0x51/0x80
• [] sysenter_past_esp+0x54/0x75
• Code: Bad EIP value.
华清远见 strace
• 4 main()
• 5 {
• 6 int fd, num, pos;
• 7 char wr_ch[200] = "This is a test of globalmem";
• 8 char rd_ch[200];
• 9 //打开 /dev/globalmem
• 10 fd = open("/dev/globalmem", O_RDWR, S_IRUSR | S_IWUSR);
• 11 if (fd != 1 )
• 12 {
• 13 //清除 globalmem
• 14 if(ioctl(fd, MEM_CLEAR, 0) < 0)
• 15 {
• 16 printf("ioctl command failed\n");
• 17 }
• 18 //读 globalmem
• 19 num = read(fd, rd_ch, 200);
• 20 printf("%d bytes read from globalmem\n",num);
• 21
• 22 //写 globalmem
• 23 num = write(fd, wr_ch, strlen(wr_ch));
• 24 printf("%d bytes written into globalmem\n",num);
• 25 ...
• 26 close(fd);
• 27 }
• 28 }
• execve("./globalmem_test", ["./globalmem_test"], [/* 24 vars */]) = 0
• ...
• open("/dev/globalmem", O_RDWR) = 3 打开
的 /dev/globalmem的 fd是 3
• ioctl(3, FIBMAP, 0) = 0
• read(3, 0xbff17920, 200) = 200 读取到 200个
字节
• ...
• write(1, "200 bytes read from globalmem\n", 30200 bytes read from
globalmem
• ) = 30 向
标准
excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载
输出设备 (fd 为 1)写入
printf中的字符串
• write(3, "This is a test of globalmem", 27) = 27
• write(1, "27 bytes written into globalmem\n", 3227 bytes written into
globalmem
• ) = 32
• ...
华清远见 源代码级调试
目标机“插桩”:
打上 kgdb补丁,这样主机上的 gdb可与目标机的 kgdb通过串口或网口通信。
使用仿真器:
仿真器可直接连接目标机的 JTAG/BDM,这样主机的 gdb就可以通过与仿真器
的通信来控制目标机
Linux
kgdb
gdb
串口, USB等
华清远见 用户空间测试
编写用户空间的程序访问设备驱动
read
write
ioctl
做成含测试菜单的程序
每一个菜单体现一种功能
华清远见 设备驱动学习方法
牢固掌握内核编程基础知识
并发、同步
内存与 I/ O访问
中断两个半部
理解复杂设备驱动架构的特点
参考同类设备驱动的源代码
动手实践
在 PC上实践 globalmem和 globalfifo
在开发板上学习真实设备的驱动
在工作中学习
华清远见 Linux设备驱动开发详解
主要出发点:
力求用最简单的实例讲解复杂的
知识点
高中化学知识点免费下载体育概论知识点下载名人传知识点免费下载线性代数知识点汇总下载高中化学知识点免费下载
,以免实例太复杂搅浑读者(驱
动理论部分)
对 Linux设备驱动多种复杂设备的框架结构进行了全面的介绍(驱动框架
部分)
更面向实际的嵌入式
工程
路基工程安全技术交底工程项目施工成本控制工程量增项单年度零星工程技术标正投影法基本原理
,讲解开发必备的软硬件基础,及开发手段
(调试与移植部分)
提供讨论与交流平台(华清远见, www.linuxdriver.cn,数个 QQ群 )
华清远见 华清远见 Linux驱动课程
嵌入式 Linux驱动初级班
通过本课程的学习,学员可以掌握 Linux下字符设备、块设备、网络设
备的驱动程序开发,同时掌握嵌入式 Linux的系统开发和
分析
定性数据统计分析pdf销售业绩分析模板建筑结构震害分析销售进度分析表京东商城竞争战略分析
方法。
嵌入式 Linux驱动开发高级班
本课程以案例教学为主,系统地介绍 Linux下有关 FrameBuffer、MMC
卡、 USB设备的驱动程序开发。
班级规模及环境
为了保证培训效果,增加互动环节,我们坚持小班授课,每期报名人
数限 15人,多余人员安排到下一期进行。人手一套开发板和开发用的
PC主机。
华清远见 让我们一起讨论!
www.farsight.com.cn
谢谢!
The success 's road