【驱动】Linux初级驱动系列框架
【系统环境搭建】
1.uboot的命令
set serverip 192.168.7.xx set ipaddr 192.168.7.xxx set bootcmd tftp 20800000 zImage\;bootm 20800000 //开发模式 set bootcmd nand read 20800000 40000 2000000\;bootm 20800000 //产品模式 set bootargs root=/dev/nfs nfsroot=192.168.7.xx:/opt/rootfs console=ttySAC0,115200 ip=192.168.7.xxx
2.内核配置与编译
2.1 修改交叉工具链,修改顶层目录的Makefile
2.3 生成默认配置,默认配置一般在arch/arm/configs
2.4 根据产品功能需求通过图形化菜单来添加自己的配置(最小系统需要dm9000网卡驱动)
2.5 编译 make zImage或make uImage
3.根文件系统的挂载
3.1 修改nfs的目录
sudo vi /etc/export sudo /etc/init.d/nfs-kerner-server restart sudo exportfs -a
【linux内核模块开发】
1.什么是linux内核模块?
实际是一个可以在系统启动后,动态加载到系统的代码
2.linux内核模块的优点?
2.1 动态加载与卸载
2.2 减少内核镜像的大小
2.3 节省开发周期
3.linux内核模块的基本组成
3.1 头文件
3.2 模块加载函数/模块入口函数
a. 初始化工作
b. 资源申请
3.3 模块卸载函数/模块出口函数
与模块加载函数做相反的事情
3.5 权限许可声明
4.linux内核模块的操作命令
加载内核模块:insmod xxx.ko
卸载内核模块:rmmod xxx
查看:lsmod
【如何将代码直接编译进内核】
1.将代码拷贝到内核的相应的目录
2.修改此目录下的Makefile,Kconfig
3.进入图形化菜单,选择相应的选项 make menuconfig
4.重新编译内核
【linux设备驱动开发】
1.linux下设备驱动的基本知识
a.一切设备皆文件
b.文件的属性:
1)设备号
2)名字
3)类型 l d - s p c b
4)权限
【linux下字符设备驱动的基本框架】
【字符设备驱动的基本知识点】
字符设备涉及的重要结构体
//驱动人员必须自己构建一个变量 struct file_operations --->表示设备的操作方法 { struct module *owner; ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); int (*open) (struct inode *, struct file *); ... }; //老的方法:不需要驱动人员构建,只要调用register_chrdev //新的方法:需要驱动人员构建 struct cdev ---->表示一个字符设备 { const struct file_operations *ops;//设备的操作方法 dev_t dev; //设备号 ... }; //1.mknod /dev/xxx c major minor //2.class_create device_create struct inode ---->表示一个设备节点:每个目录或者每个文件都对应一个inode结构体(静态的) { dev_t i_rdev; //设备号 const struct file_operations *i_fop; /* former ->i_op->default_file_ops def_chr_fops */ struct cdev *i_cdev; ... }; struct file --->表示一个打开的文件:每一个文件被打开一次,都会创建一个file结构体(动态的) { loff_t f_pos; //文件的偏移量 unsigned int f_flags; //标号 open("/dev/xxx",O_RDWR|O_NONBLOCK) file->f_flags |=O_RDWR|O_NONBLOCK const struct file_operations *f_op; //文件的操作方法 void *private_data; ... };
【字符设备驱动涉及的关键函数】
a.申请主设备号
老的方法:register_chrdev(major,name,file_Operations); ----->unregister_chrdev 新的方法:register_chrdev_region(dev_t,count,name); ----->unregister_chrdev_region alloc_chrdev_region(dev_t *,baseminor,count,name);
b.创建cdev结构体的函数
cdev_alloc cdev_init(struct cdev*, file_operations); cdev_add cdev_del
c.创建设备文件的函数
手动创建:mknod /dev/xxx c major minor 动态创建:class_create(owner,name); device_create
d.设备号的操作
设备号用dev_t来表示,它是一个无符号的32位整形数,其中高12位为主设备号,低20位为次设备号
MKDEV(major,minor)
MAJRO(dev_t)
MINOR(dev_t)
iminor(struct inode);
imajor(struct inode);
e.将物理地址映射为虚拟地址
虚拟地址=ioremap(物理地址,大小) ---->iounmap(虚拟指针)
f.用户空间到内核空间的数据交互
copy_from_user
copy_to_user
【字符设备驱动的编写流程】
1)实现模块加载函数
a.申请主设备号
b.创建字符设备结构体(struct cdev),注册字符设备
c.创建设备文件
d.将物理地址映射为虚拟地址
2)实现模块卸载函数
3)构建file_operations结构体
4)实现操作硬件的方法
xxx_open xxx_write xxx_read
【linux下中断编程】
1.注册中断/申请中断
/*param1:中断号 mach/irqs.h *param2:中断服务程序 typedef irqreturn_t (*irq_handler_t)(int, void *); linux/irqreturn.h * enum irqreturn { * IRQ_NONE, //如果中断处理失败则返回 0 * IRQ_HANDLED, //如果中断处理成功则返回 1 * IRQ_WAKE_THREAD, * }; * * typedef enum irqreturn irqreturn_t; *param2:中断触发方式 linux/interrupt.h *param3:名字 *param4:id号,一般共享中断才会用, */ ret = request_irq(IRQ_EINT(1), irq_handler_t handler, unsigned long flags, const char * name, void * dev)
2.释放中断
free_irq(unsigned int irq, void * dev_id)
3.实现中断服务程序
typedef irqreturn_t (*irq_handler_t)(int, void *);
【阻塞与非阻塞机制】
2.1 进程的状态转换(就绪态,运行态,休眠态,暂停态,僵死态,死亡态)
2.2 如何让进程进入休眠态
a.改变进程状态 ------>set_current_state(state_value);
b.将进程挂入等待队列 ----->add_wait_queue(wait_queue_head_t *q,wait_queue_t *wq);
c.让出CPU资源,调度 ------>schedule();
2.3 休眠的原理:
等待队列头:
struct __wait_queue_head { spinlock_t lock; /*锁,用来同步*/ struct list_head task_list;/*链表*/ }; typedef struct __wait_queue_head wait_queue_head_t;
任务节点:
struct __wait_queue { unsigned int flags; #define WQ_FLAG_EXCLUSIVE 0x01 void *private; /*应该指向当前进程*/ wait_queue_func_t func; /*唤醒函数*/ struct list_head task_list;/*任务链表*/ }; typedef struct __wait_queue wait_queue_t;
2.4 对队列头与任务节点的操作函数与宏:
任务节点的定义:
方法一:
/*定义一个节点,并初始化任务节点*/ #define __WAITQUEUE_INITIALIZER(name, tsk) { \ .private = tsk, \ .func = default_wake_function, \ .task_list = { NULL, NULL } } #define DECLARE_WAITQUEUE(name, tsk) \ wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
方法二:
:/*定义并初始化一个任务节点*/ #define DEFINE_WAIT_FUNC(name, function) \ wait_queue_t name = { \ .private = current, \ .func = function, \ .task_list = LIST_HEAD_INIT((name).task_list), \ } #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)
等待队列头的定义:
方法一:
/*初始化等待队列头*/ #define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \ .lock = __SPIN_LOCK_UNLOCKED(name.lock), \ .task_list = { &(name).task_list, &(name).task_list } } /*定义等待队列头,并且初始化等待队列头*/ #define DECLARE_WAIT_QUEUE_HEAD(name) \ wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
方法二:
/*定义等待队列头*/ wait_queue_head_t *q; /*初始化等待队列头*/ #define init_waitqueue_head(q) \ do { \ static struct lock_class_key __key; \ \ __init_waitqueue_head((q), &__key); \ } while (0)
将任务节点加入队列:
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
【如何在驱动添加阻塞机制】
a. 定义一个等待队列头
b. 初始化等待队列头
c. 在适当的地方实现阻塞
wait_event_interruptiable(wq,condition);
d. 在适当的地方唤醒
wake_up_interruptiable(wq);
【异步通知机制】
【linux下poll机制的工作原理】
app:ret=poll(fds,1,3000); //标准的系统调用接口
vfs:long sys_poll(struct pollfd __user *ufds, //系统调用号168 unsigned int nfds,long timeout_msecs) /*1.转换时间,转换为以jiffies(内核时间片)为单位的时间*/ do_sys_poll(ufds, nfds, &timeout_jiffies); poll_initwait(&table); /*3.查询是否有事件*/ do_poll(nfds, head, &table, timeout); for (;;) { /*3.1 改变进程状态*/ set_current_state(TASK_INTERRUPTIBLE); //可被信号中断的休眠 /*3.2 将进程挂入等待队列*/ if (do_pollfd(pfd, pt)) { if (file->f_op && file->f_op->poll) //在此调用驱动程序的poll函数 mask = file->f_op->poll(file, pwait); return mask; count++; pt = NULL; } /*3.3 1)count>0 2)时间用完 3)有信号*/ if (count || !*timeout || signal_pending(current)) break; /*3.4 时间减少*/ *timeout -= __timeout; /*3.5 调度,休眠(定时休眠)*/ schedule_timeout(__timeout); } __set_current_state(TASK_RUNNING);
【poll机制的驱动编程流程】
1.定义一个等待队列头 wait_queue_head_t buttons_wq;
2.初始化等待队列头 init_waitqueue_head(&buttons_wq);
3.实现poll的功能函数
a. 将进程挂入等待队列,但是不会休眠
poll_wait(file, &buttons_wq, wait);
b. 判断是否有事件
4.在中断服务程序中实现唤醒
wake_up_interruptible(&buttons_wq);
【阻塞-异步通知-poll轮询比较】
如何在驱动中添加阻塞与非阻塞机制
异步通知的编程流程
poll机制的编程流程
【如何将驱动加入内核】
1.将驱动加入内核目录的驱动目录的相应的目录下
cp hello.c /home/farsight/work/linux_source/linux-2.6.35.5/drivers/char/
同时,任何一个目录中都会有一个Kconfig
2.修改相应目录下的Makefile(参照)
在linux-2.6.35.5/driver/char/,修改Makefile
obj-$(CONFIG_HELLO) += hello.o //在其中添加如下项
【解释Kconfig】
模型1:
config N_GSM //菜单项 tristate "GSM MUX line discipline support (EXPERIMENTAL)" //菜单项目名字 tristate-属性,表示三态<> y-编译到内核镜像 n-不编译 m-编译成模块 depends on EXPERIMENTAL //依赖 depends on NET help //帮助文档 This line discipline provides support for the GSM MUX protocol and presents the mux as a set of 61 individual tty devices.
模型2:
config RIO_OLDPCI bool "Support really old RIO/PCI cards" depends on RIO help Older RIO PCI cards need some initialization-time configuration t o determine the IRQ and some control addresses. If you have a RIO and this doesn't seem to work, try setting this to Y.
模型3:
config HVC_CONSOLE bool "pSeries Hypervisor Virtual Console support" depends on PPC_PSERIES select HVC_DRIVER //帮你选择 select HVC_IRQ help pSeries machines when partitioned support a hypervisor virtual console. This driver allows each pSeries partition to have a cons ole which is accessed via the HMC.
模型4:
config LEGACY_PTYS bool "Legacy (BSD) PTY support" default y //默认选上了 ---help--- A pseudo terminal (PTY) is a software device consisting of two halves: a master and a slave. The slave device behaves identical to a physical terminal; the master device is used by a process to
3.在图像化菜单上,做一个菜单项,必须修改Kconfig
config HELLO tristate "farsight hello module" ---help--- this is a simple hello module by liucw
4.选择配置项
make menuconfig
@成鹏致远 | 2013-08-20