Linux驱动开发-简单例子
来源:互联网 发布:c语言可以做什么软件 编辑:程序博客网 时间:2024/05/20 05:29
1、软件系统分为:应用程序、库、操作系统(内核)、驱动程序,开发人员专注某一层,了解邻层的接口。如,应用程序调用库函数open,库根据open传入的参数执行swi指令引起CPU异常进入内核。内核的异常处理函数根据参数找到相应驱动程序。内核与驱动程序没有界限,因为驱动程序最终是要编进内核。驱动程序从不主动运行。在有MMU的系统中,应用程序处于用户空间,驱动程序处于内核空间。
2、Linux外设分为:字符设备(读写以字节方式进行)、块设备(数据读写以块方式,数据有格式)、网络接口(数据读写是大小不固定的块)。
3、Linux设备驱动程序开发步骤
(1)初始化驱动程序,如向内核注册这个驱动程序,这样应用程序传入文件名时,内核才能找到相应的驱动程序。
(2)设计要实现的操作函数,如open等。
(3)实现中断服务
(4)编译驱动程序到内核,如果动态编译为模块,则用insmod rmmod命令进行加载和卸载。加载:调用模块的初始化函数,向内核注册驱动程序。卸载:调用模块清除函数。
(5)测试
4、应用程序使用统一的接口函数(系统调用)调用硬件启动程序。字符设备驱动程序的函数集合在 include/linux/fs.h 的file_operations 结构中
//为驱动函数规定统一以文件操作的接口,如open、read ....//当应用程序使用open函数打开某个设备,就会调用 file_operations 中的open函数。从这个角度,编写字符设备驱动程序就是为具体硬件的file_operations 结构编写需要的函数。struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*dir_notify)(struct file *filp, unsigned long arg); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);};
5、安装驱动程序时调用初始化函数,把驱动出现的file_operations 结构和主设备号向内核注册。内核为字符设备保存一个数组,主设备号为数组标号。加载即在对于标号填入对应设备file_operations 地址,卸载相反。
//对于字符设备使用如下函数进行注册int register_chrdev(unsigned int major, consr char *name, struct file_operations *fops);//之后当应用程序操作设备时,Linux系统就会根据设备文件类型、主设备号找到file_operations
简单的驱动程序编写(不涉及中断、select机制、fasync异步通知机制)
(1)编写驱动初始化函数
(2)构造file_operations 中成员函数
6、LED驱动程序分析
(1)初始化,指定加载和卸载函数
//执行 insmod s3c24xx_leds 就会调用该函数static int __init s3c24xx_leds_init(void){ int ret; /* 注册字符设备驱动程序 * 参数为主设备号、设备名字、file_operations结构; * 这样,主设备号就和具体的file_operations结构联系起来了, * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数 * LED_MAJOR可以设为0,表示由内核自动分配主设备号 */ ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops); if (ret < 0) { printk(DEVICE_NAME " can't register major number\n"); return ret; } printk(DEVICE_NAME " initialized\n"); return 0;} // 执行”rmmod s3c24xx_leds.ko”命令时就会调用这个函数 static void __exit s3c24xx_leds_exit(void){ /* 卸载驱动程序 */ unregister_chrdev(LED_MAJOR, DEVICE_NAME);}/* 这两行指定驱动程序的初始化函数和卸载函数 *///要是不适用这2行就要把加载和卸载函数名改为init_module和cleanup_modulemodule_init(s3c24xx_leds_init);module_exit(s3c24xx_leds_exit);
(2)file_operations 结构
static struct file_operations s3c24xx_leds_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = s3c24xx_leds_open, .ioctl = s3c24xx_leds_ioctl,};
(3)应用程序对设备文件/dev/leds执行open和ioclt时会调用的函数
// 应用程序对设备文件/dev/leds执行open(...)时,就会调用s3c24xx_leds_open函数 //open即设置LED引脚为输出static int s3c24xx_leds_open(struct inode *inode, struct file *file){ int i; for (i = 0; i < 4; i++) { // 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能,该函数在内核中实现 s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]); } return 0;}/* 应用程序对设备文件/dev/leds执行ioclt(...)时, * 就会调用s3c24xx_leds_ioctl函数 */ //根据命令,实现LED的开和关,open返回inode和file给ioctlstatic int s3c24xx_leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){ if (arg > 4) { return -EINVAL; } switch(cmd) { case IOCTL_LED_ON: // 设置指定引脚的输出电平为0,该函数在内核中实现 s3c2410_gpio_setpin(led_table[arg], 0); return 0; case IOCTL_LED_OFF: // 设置指定引脚的输出电平为1,该函数在内核中实现 s3c2410_gpio_setpin(led_table[arg], 1); return 0; default: return -EINVAL; }}
7、驱动编译,编译在PC上,PC上有未编译的linux目录
cat /proc/devices 查看已有设备
(1)在drivers\char下加入文件s3c24xx_leds.c;
(2)在drivers\char\Makefile中加一行
obj-m += s3c24xx_leds.o
(3)在内核根目录下执行make modules生成drivers\chars\s3c24xx_leds.ko
(4)把s3c24xx_leds.ko(以网络传输等方式)放到开发板文件系统/lib/modules/2.6.22.6/下
(5)这是就可以使用insmod s3c24xx_leds 和rmmod s3c24xx_leds命令进行加载和卸载。
8、测试程序(应用程序)
(1)在PC上编译生成可执行文件,把它放到开发板文件系统/user/bin目录下
(2)在开发板文件系统中建立设备文件
mknod /dev/leds c 231 0
(3)用命令测试
led_test 1 onled_test 2 off
应用程序的open和ioctl等系统调用,他们的参数和驱动程序中相应函数的参数不是一一对应的,经过了内核文件层的转换。
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/ioctl.h>#define IOCTL_LED_ON 0#define IOCTL_LED_OFF 1void usage(char *exename){ printf("Usage:\n"); printf(" %s <led_no> <on/off>\n", exename); printf(" led_no = 1, 2, 3 or 4\n");}int main(int argc, char **argv){ unsigned int led_no; int fd = -1; if (argc != 3) goto err; fd = open("/dev/leds", 0); // 打开设备,根据/dev/leds提取设备类型、主设备号,根据这些可以找到对应file_operations if (fd < 0) { printf("Can't open /dev/leds\n"); return -1; } led_no = strtoul(argv[1], 0, 0) - 1; // 操作哪个LED? if (led_no > 3) goto err; if (!strcmp(argv[2], "on")) { ioctl(fd, IOCTL_LED_ON, led_no); // 点亮它 } else if (!strcmp(argv[2], "off")) { ioctl(fd, IOCTL_LED_OFF, led_no); // 熄灭它 } else { goto err; } close(fd); return 0;err: if (fd > 0) close(fd); usage(argv[0]); return -1;}
9、Makefile,把开发版根文件挂载到PC,PC直接编译无需下载
KERN_DIR = /work/linux-2.6//内核目录all: make -C $(KERN_DIR ) M = 'pwd' module // -C表示到KERN_DIR 目录下执行Makefileboj-m += s3c24xx_led.o
- Linux驱动开发-简单例子
- 简单linux驱动小例子
- linux内核定时器驱动一个简单例子
- linux驱动_最简单的例子
- linux 驱动开发简单声明
- Linux驱动开发一:简单的驱动
- 测试驱动开发随笔------一个最简单的例子
- Android 驱动和系统开发. 一个简单的例子
- Android 驱动跟系统开发 1. 一个简单的例子
- linux 一个简单的字符设备驱动例子
- linux 一个简单的字符设备驱动例子
- Linux驱动开发学习的简单步骤
- linux下简单的设备驱动开发
- Linux设备驱动开发详解-Note(9) --- helloword例子
- linux字符驱动例子
- 字符设备驱动简单例子
- WDM模式驱动简单例子
- linux字符设备驱动例子
- springboot的快速入门
- 编译安装cmake3.9.1
- 树状数组应用-冒泡排序的交换次数
- 每天一个linux命令(13):less 命令
- matplotlib解决中文编码的终极方案
- Linux驱动开发-简单例子
- 图解VIM常用操作
- 557. Reverse Words in a String III
- CCF 201612-2 工资计算
- springBoot(四)整合之MyBatis整合
- C++函数中那些不可以被声明为虚函数的函数
- jquery 调用 click 事件 的 三种 方式
- Mac安装lua环境配置
- java非捕获组-肯定式向前查找(?=)和肯定式向后查找(?<=)