
f79821c9919935e06b7406bed6997331.ppt
- Количество слайдов: 39
Linux设备驱动程序 04/05/2006 应忍冬
内容 • • • 设备分类 设备驱动程序的框架 字符型设备 网络设备 文件系统 – User Space File System • • USB设备 Frame. Buffer例子和使用 Debug原理和Debug方法 常用设备/fb/ram/loopback/zero
设备驱动程序的任务 • • 设备初始化 硬件操作和管理 外部硬件和内核空间的数据传递 内核空间和用户空间的数据传递
设备驱动程序的功能 用 户 空 间 内 核 空 间 用户程序 存储缓冲 设备驱动程序 外部硬件
用户态程序 vs 内核态程序 用户程序 • 权限受限 • 虚拟运行环境 –逻辑地址 –关键资源访问受监管 • 函数调用由用户控制 内核程序 • 最高权限 • 实际的运行环境 –物理地址 –可访问所有资源 • 函数由内核直接调用 可以运行驱动程序
地址映射与物理地址访问 用户进程1 用户进程2 用户进程3 虚拟地址映射 物理地址空间 用户利用指针访问的是虚地址,不是物理地址, IO设备的物理地址可能是用户进程不可触及的
直接访问内核内存(/dev/kmem) kmfd = open("/dev/kmem", O_RDONLY ); lseek( kmfd, offset, SEEK_SET ); read( kmfd, byte. Array. Len ); close(kmfd); • 直接访问内核地址(内核态的虚地址) • 一般内核地址起始于0 x. C 0000000
直接访问物理地址(/dev/mem) mem_fd = open("/dev/mem", O_RDONLY ); b=mmap(0, 0 x 10000, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, 0 x. A 0000) … close(memfd); mmap将文件中的数据映射成数组 Pointer b 这里是将物理内存(由特殊文件/dev/mem访问)映射 成指针b指向的数组。 注意,指针b的值不一定是 0 x. A 0000,它是和物理地址 0 x. A 0000对应的用户态的虚拟地址 Linux中/dev/mem主要是用于设备内存的访问(比如显 卡内存),而不是普通存储器 0 x. A 0000 0 x. B 0000
直接访问IO端口(/dev/port) port_fd = open("/dev/port", O_RDWR); lseek(port_fd, port_addr, SEEK_SET); read(port_fd, …); write(port_fd, …); close(port_fd); • 注意:不能用fopen/fread/fwrite/fclose因 为它们有数据缓冲,对读写操作不是立即 完成的
outb()/outw()/inb()/inw()函数 • outb(value, port); inb(port); // 8 -bit • outw(value, port); inw(port); // 16 -bit • 访问时间大约 1 us #include <stdio. h> #include <unistd. h> #include <asm/io. h> #define BASEPORT 0 x 378 // printer int main() { ioperm(BASEPORT, 3, 1)); // get access permission outb(0, BASEPORT); usleep(100000); printf("status: %dn", inb(BASEPORT + 1)); ioperm(BASEPORT, 3, 0)); // give up exit(0); • ioperm(from, num, turn_on) • 用ioperm申请的操作端口地址在 0 x 000~0 x 3 FF,利用 } iopl()可以申请所有的端口地址 • 必须以root运行 • 用 “gcc -02 –o xxx. elf xxx. c” 编译
设备驱动程序内访问设备地址 • 设备驱动程序可以通过指针访问设备地址 • 设备驱动程序接触到的还是虚拟地址,但 对于外界设备有固定的设备地址映射(设 备的地址在移植Linux时候确定) 设备驱动程序 虚拟地址映射 物理内存地址空间 设备地址映射 设备地址空间
直接访问IO端口 vs 设备驱动程序 • • IO直接访问 用户态 程序编写/调试简单 查询模式,响应慢 设备共享管理困难 • • 设备驱动访问 核心态 编程调试困难 可用中断模式访问、 快 设备共享管理简单( 由内核帮助完成)
设备分类 • 字符设备 –鼠标、串口、游戏杆 • 块设备 –磁盘、打印机 • 网络设备 –由BSD Socket访问
字符设备 vs 块设备 字符设备 • 字符设备发出读/写请 求时,对应的硬件I/O 一般立即发生。 • 数据缓冲可有可无 • ADC/DAC、按钮、LED、 传感器等 块设备 • 利用一块系统内存作 缓冲区,一般读写由 缓冲区直接提供,尽 量减少IO操作 • 针对磁盘等慢速设备
可装卸的设备驱动程序和 静态连接到内核的设备驱动程序 • 静态连接到内核的设备驱动程序 –修改配置文件、重新编译和安装内核 • 可装卸的设备驱动程序 – insmod – rmmod – lsmod 装载 卸载 查询
Linux对硬件设备的抽象 设备文件 • Open/Close/Read/Write • 例子 –/dev/mouse –/dev/lp 0
驱动程序与设备文件 用open/read/write/close 等命令访问 用户程序 设备 文件 用mknod 命令创建 通过主设备号 找到设备驱动程序 用insmod命令安装, 或直接编译到内核中
驱动程序注册与注销 驱动程序 代码结构 设备文件的操作函数 (*open)() (*write)() (*flush)() (*llseek)() … 中断服务程序
LED设备驱动程序的例子 CPU
struct file_operations LED_fops = { read: LED_read, write: LED_write, open: LED_open, release: LED_release, }; 程序列表 (1) int LED_init_module(void) { SET_MODULE_OWNER(&LED_fops); LED_major = register_chrdev(0, "LED", &LED_fops); LED_off(); LED_status=0; return 0; } void LED_cleanup_module(void) { unregister_chrdev(LED_major, "LED"); } module_init(LED_init_module); module_exit(LED_cleanup_module);
程序列表 (2) int LED_open(struct inode *inode, struct file *filp) { printk("LED_open()n"); MOD_INC_USE_COUNT; return 0; } int LED_release(struct inode *inode, struct file *filp) { printk(“LED_release()n“); MOD_DEC_USE_COUNT; return 0; }
程序列表 (3) ssize_t LED_read (struct file *filp, char *buf, size_t count, loff_t *f_pos) { int i; for (i=0; i<count; i++) *((char*)(buf+i)) = LED_Status; return count; } ssize_t LED_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) { int i; for (i=0; i<count; i++) if (*((char*)(buf+i))) Data->LED_on(); else Data->LED_off(); return count; } (*((volatile unsigned int *)(0 x. XXXX))) |= MASK; (*((volatile unsigned int *)(0 x. XXXX))) &=~MASK;
#ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #include <linux/config. h> #include <linux/module. h> #include <linux/sched. h> #include <linux/kernel. h> #include <linux/malloc. h> #include <linux/errno. h> #include <linux/types. h> #include <linux/interrupt. h> #include <linux/in. h> #include <linux/netdevice. h> #include <linux/etherdevice. h> #include <linux/ip. h> #include <linux/tcp. h> #include <linux/skbuff. h> #include <sysdep. h> #include <linux/ioctl. h> #include <linux/in 6. h> #include <asm/checksum. h> MODULE_AUTHOR("Rendong Ying"); int LED_major, LED_status; 程序列表 (4) 头文件
程序编译 (Makefile) CC = arm-elf-linux-gcc LD = arm-elf-linux-ld INCLUDE = /usr/local/src/bsp. Linux/include LIB_INC = /usr/local/lib/gcc-lib/arm-elf-linux/2. 95. 3/include CFLAGS = -O 6 -Wall -DCONFIG_KERNELD -DMODULE -D__KERNEL__ -DLinux -nostdinc -I- -I. -I$(INCLUDE) -idirafter $(LIB_INC) LED. o: LED. c $(CC) $(CFLAGS) -c LED. c clean: rm -f LED. o 生成o文件
设备装载和设备文件建立 • chmod +x /tmp/LED. o • /sbin/insmod -f. /LED. o • cat /proc/devices得到装入内核的主 设备号 • mknod /dev/Lamp c Num 1 Num 2 Num 1为主设备号 Num 2为次设备号 强制安装,忽略版本检查
设备的测试和使用 • 命令行 开启printk,也可以从/var/log/messages看printk的记录 echo 8 > /proc/sys/kernel/printk cat /dev/Lamp cat > /dev/Lamp • 程序 void main() { int fd=open(“/dev/Lamp, O_RDWR); write(fd, &data, 1); close(fd); }
设备卸载 /sbin/rmmod LED rm -f /dev/Lamp Function of MOD_INC_USE_COUNT; MOD_DEC_USE_COUNT;
复杂的设备 驱动程序 用户数 据空间 内核数据 缓冲区 驱动程序注册与注销 (注册/注销 设备、中断) 设备文件的操作函数 (*open)() (*write)() (*flush)() (*llseek)() … 中断服务程序
复杂设备驱动程序的例子 (USB Device) 中断资源申请和释放 • if (request_irq(USB_INTR_SOURCE 1, usb_ep 1_int, SA_INTERRUPT, "USB EP 1", 0) < 0) printk("Int. req. failed !n"); • free_irq(USB_INTR_SOURCE 0, 0); cat /proc/interrupts
中断服务程序 • 没有返回参数 • 简短快速 void usb_ep 1_int(int irq, void *dev_id, struct pt_regs *regs) { //… }
数据接收中断服务程序 void usb_ep 1_int(int irq, void *dev_id, struct pt_regs *regs) { read_data_from_hardware_FIFO(); put_data_into_data_buffer(); }
数据发送中断服务程序 void usb_ep 2_int(int irq, void *dev_id, struct pt_regs *regs) { read_data_from_buffer(); send_data_hardware_FIFO (); }
设备文件接口函数(read) ssize_t usb_ep 1_read (struct file *filp, char *buf, size_t count, loff_t *f_pos) { if (data_buffer_empty()) return 0; else copy_data_to_user_space(); return data_copyed; } copy_to_user(user_buf, device_driver_buf, size);
设备文件接口函数 (read, blocking mode) ssize_t usb_ep 1_read (struct file *filp, char *buf, size_t count, loff_t *f_pos) { while(device_driver_buf_empty()) { if (wait_event_interruptible(q_ep 2, device_driver_buf_not_empty)) return -ERESTARTSYS; } copy_data_to_user_space(); return data_copyed; } wait_queue_head_t rq_EP 2; init_waitqueue_head(&rq_EP 2);
设备文件接口函数(write) ssize_t usb_ep 2_write (struct file *filp, char *buf, size_t count, loff_t *f_pos) { if (data_buffer_full()) return 0; else copy_data_to_device_driver_buf(); if (no_transmission_now) send_1 st_data(); return data_copyed; } copy_from_user(device_driver_buf, user_buf, size);
内存申请 • malloc ? X • kmalloc • kfree • vmalloc • vfree
禁止设备打开多次 int LED_flag; int LED_init_module(void) { LED_flag=0; … } int LED_open(struct inode *inode, struct file *filp) { if (LED_flag=0) { LED_flag=1; MOD_INC_USE_COUNT; return 0; } else } return -ENODEV; int LED_release(struct inode *inode, struct file *filp) { LED_flag=0; MOD_DEC_USE_COUNT; return 0; }
同一设备驱动管理几个接口 应用程序 Serial Port Device Driver UART 0
同一设备驱动管理几个接口 int dev_open(struct inode *inode, struct file *filp) { int minor = MINOR(inode->i_rdev); filp->private_data=sub_dev_dat[minor]; … } ssize_t dev_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) { switch(*(filp->private_data)) { … } }
f79821c9919935e06b7406bed6997331.ppt