1. 概要
u-boot:启动内核
内核:启动应用程序
应用程序:读写文件,点灯,获取按键值等等。
1.1应用程序与内核、驱动的关系
大家各司其职,显然应用程序不应该关心内部实现方式,使用标准的接口open, read, write等操作文件和设备(设备也是一种文件)。
以一个简单的应用程序为例。
int main()
{
int fd1, fd2;
int val = 1;
fd1 = open("/dev/led", O_RDWR);
write(fd1, &val, 4);
fd2 = open("hello.txt", O_RDWR);
write(fd2, &val, 4);
}
当应用程序调用C库中open,read,write等函数时,会通过汇编指令swi val引发异常,异常处理函数根据发生中断的原因调用不同的处理函数,sys_open, sys_read, sys_write即VFS(virtual file system)层。
1.2框架
1.写read,write函数
2.定义file_operations结构,填充内容
3.把结构告诉内核register_chrdev
4.驱动入口函数first_drv_init
5.使用宏修饰入口函数module_init(first_drv_init)
在VFS中,有一个大数组,每个成员代表一个file_operations结构,写好驱动后,将驱动注册到数组中。
2.第一个驱动程序:点灯
写好第一个驱动程序。
C程序
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static int first_drv_open(struct inode *inode, struct file *file)
{
printk("first_drv_open\n");
return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
printk("first_drv_write\n");
return 0;
}
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = first_drv_open,
.write = first_drv_write,
};
static int first_drv_init(void)
{
register_chrdev(111, "first_drv", &first_drv_fops); // 注册驱动
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(111, "first_drv"); // 卸载驱动
}
module_init(first_drv_init);
module_exit(first_drv_exit);
Makefile
KERN_DIR = /work/system/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += first_drv.o
编译
book@book-desktop:/work/my_drivers/first_drv$ make
make -C /work/system/linux-2.6.22.6 M=`pwd` modules
make[1]: Entering directory `/work/system/linux-2.6.22.6'
CC [M] /work/my_drivers/first_drv/first_drv.o
Building modules, stage 2.
MODPOST 1 modules
LD [M] /work/my_drivers/first_drv/first_drv.ko
make[1]: Leaving directory `/work/system/linux-2.6.22.6'
拷贝first_drv.ko到NFS文件系统中
book@book-desktop:/work/my_drivers/first_drv$ cp first_drv.ko /work/nfs_root/first_fs
开发板就可以看到first_drv.ko文件了。
# ls
bin first_drv.ko lib sbin
dev hello linuxrc sys
etc hello.c proc usr
#
开发板注册驱动程序,有个警告,暂时先忽略。
# insmod first_drv.ko
first_drv: module license 'unspecified' taints kernel.
查看设备,已经有了我们写好的111驱动了。
# cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
6 lp
7 vcs
10 misc
13 input
14 sound
29 fb
90 mtd
99 ppdev
111 first_drv
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 s3c2410_serial
253 usb_endpoint
254 rtc
Block devices:
1 ramdisk
7 loop
8 sd
31 mtdblock
65 sd
66 sd
67 sd
68 sd
69 sd
70 sd
71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
#
测试程序,其中名称可以任意
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/xxx", O_RDWR);
if(fd < 0)
{
printf("can't open!\n");
}
write(fd, &val, 4);
return 0;
}
编译,并拷贝到NFS文件系统中
book@book-desktop:/work/my_drivers/first_drv$ arm-linux-gcc -o firstdrvtest firstdrvtest.c
book@book-desktop:/work/my_drivers/first_drv$ cp firstdrvtest /work/nfs_root/first_fs
开发板上执行测试程序
# ./firstdrvtest
can't open!
无法打开,因为还没有该设备的节点。创建节点,包含名称,类型,主设备号,次备号,重新执行,OK。
# mknod /dev/xxx c 111 0
#
# ./firstdrvtest
first_drv_open
first_drv_write
#
3. 完善第一个驱动程序
注册驱动的时候,如果主设备号设置为0,那么系统会自动分配主设备号
major = register_chrdev(0, “first_drv”, &first_drv_fops);
由于主设备号已经变更,需要卸载,重新安装驱动程序
# lsmod
Module Size Used by Tainted: P
first_drv 1728 0
# rmmod first_drv
# lsmod
Module Size Used by Tainted: P
#
# rm /dev/xxx
# mknod /dev/xxx c 252 0
# ls -l /dev/xxx
crw-r--r-- 1 0 0 252, 0 Jan 1 02:26 /dev/xxx
# ./firstdrvtest
first_drv_open
first_drv_write
#
这种方法,每次都要卸载,重新安装,不方便。
自动创建
每次创建一个设备都会在系统目录下增加设备信息。
# ls /sys/devices/
platform system
#
mdev机制,根据系统信息,自动创建设备节点。
重写驱动的入口和出口函数
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册驱动
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 卸载驱动
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
}
这样,就可以自动创建/dev/xyz设备节点。重新编译。
开发板上卸载驱动,重新加载驱动,查看设备节点。
# lsmod
Module Size Used by Tainted: P
first_drv 1796 0
# rmmod first_drv
# lsmod
Module Size Used by Tainted: P
# insmod ./first_drv.ko
# lsmod
Module Size Used by Tainted: P
first_drv 2156 0
# ls -l /dev/xyz
crw-rw---- 1 0 0 252, 0 Jan 1 02:47 /dev/xyz
如何产生主设备号的?其实,在加载过程中,由于使用了class_create和class_device_create,在系统目录下生成了该设备节点信息。
# cd /sys/
# ls
block class firmware kernel power
bus devices fs module
# cd class/
# ls
firstdrv mem ppdev scsi_host usb_host
graphics misc printer sound vc
hwmon mmc_host rtc spi_master vtconsole
i2c-adapter mtd scsi_device tty
input net scsi_disk usb_endpoint
# cd firstdrv/
# ls
xyz
# cd xyz/
# ls
dev subsystem uevent
# cat dev
252:0
#
在脚本文件中,定义支持热插拔。内核一旦有驱动加载或下载时,/proc/sys/kernel/hotplug这个程序会自动加载,卸载设备。
# vi /etc/init.d/rcS
#mount -t proc none /proc
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
这样,应用程序只需要知道设备名称就可以。