在android系统中,以上几个小节文章中,把它的分层做了一些简单的描述,接下就是内核驱动相关的内容。这是一个自由的世界,当然很复杂。也正是因为自由,才可以构建不同的操作系统世界,android是其中之一。不管android是不是传统意义上的linux上的操作系统,它毕竟实现了一些很有用的东西。
对于内核这里不想详细展开,毕竟自己的功底也有限。单说一些简单的驱动。事实上,自己所接触的驱动颇为简单,无外乎管脚的控制。下面以一个简单char驱动来说明android底层的东西,其实就是linux驱动内容:
对于驱动开发,那么肯定是要看“ Linux 设备驱动程序第三版 2.6 ”( LDDP )这本书了。目前已经是 2.6 版本了,有中文版本。
看完这本书,再看以下例子,你会觉得非常简单。不看那本书,按照以下例子,当然也可以写一个简单的驱动程序,需要注意的下面的例子是字符驱动。
写 linux 下驱动有一个框架,一般来说完成以下几个函数就可以了。
xxx_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
xxx_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp)
xxx_open(struct inode *inode, struct file *filp)
xxx_release(struct inode *inode, struct file *filp)
xxx_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
static int __init xxx_init(void)
static void __exit xxx_exit(void)
module_init(xxx_init);
module_exit(xxx_exit);
实际应用中,字符驱动 write/read 函数很少用到。一般也可以不实现,可以由 ioctl 函数来实现。
驱动与应用程序接口函数对应,看驱动函数的名字就知道了。
xxx_read <---> read
xxx_write <---> write
xxx_open <---> open
xxx_release <---> close
xxx_ioctl <---> ioctl
也就是说,在应用程序调用 read,write,open,ioctl 函数,最终就是调用其驱动的对应的函数。
在 insmod 时,会调用 xxx_init
在 rmmod 时,会调用 xxx_exit
对于 xxx_init 函数,那么要实现的东西比较多,比如 io 口的设置,或其他变量的初始赋值,驱动的注册,设备节点的创建等。相应地, xxx_exit 就完成 xxx_init 所做事下结逆过程。此处详说:
每个驱动都有一个主设备号和次设备号,可以固定设置,但不能有冲突。最好的方法是系统分配,这也是 LDDP 作者所建议的。
static struct file_operations xxx_fops =
{
.owner = THIS_MODULE,
.open = xxx_open,
.release = xxx_release,
.ioctl = xxx_ioctl,
.read = xxx_read,
.write = xxx_write,
};
struct cdev xxx_cdev;
struct class *xxx_class;
Int xxx_init(void)
{
int res, err;
dev_t dev = MKDEV(xxx_major, 0);
if (xxx_major)
{
res = register_chrdev_region(dev, 1, DEVICE_NAME); // 固定分配
}
else
{
res = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);// 动态分配
xxx_major = MAJOR(dev);
}
if (res < 0)
{
printk("xxx_device: unable to get major %d/n", xxx_major);
return res;
}
if (xxx_major == 0)
{
xxx_major = res;
}
cdev_init(&xxx_cdev, &xxx_fops);
xxx_cdev.owner = THIS_MODULE;
xxx_cdev.ops = &xxx_fops;
err = cdev_add (&xxx_cdev, dev, 1); // 注册 cdev
if (err)
{
printk ("Error %d adding xxx%d", err, 0);
}
xxx_class = class_create(THIS_MODULE, "xxx_class");
device_create(xxx_class, NULL, dev, DEVICE_NAME, "xxx%d", 0);
other_init();
}
int xxx_exit(void)
{
xxx_release(NULL, NULL);
cdev_del(&xxx_cdev);
device_destroy(xxx_class, MKDEV(xxx_major, 0));
class_destroy(xxx_class);
unregister_chrdev_region(MKDEV(xxx_major, 0), 1);
}
对于 xxx_open, 可以简单提示一些信息,表示此驱动加载。 同样 xxx_release 也可以用简单的提示一些信息,表明关闭驱动了。
重点是在 xxx_ioctl 函数,我们基本所有的操作就在这里了。
static int epi_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
char val = 1;
switch (cmd)
{
case IO_CMD_TEST_SET:
copy_from_user(&val, (char *)arg, 1);
printk(“%d”, val);
break;
case IO_CMD_TEST_GET:
copy_to_user( (char *)arg, &val, 1);
break;
default:
break;
}
return 0;
}
以上所实现的是从 arg 中取得数据,把数据放到 arg 中两个功能。用到了 copy_from_usr 和 copy_to_usr 两个函数。为什么有这两个函数:是因为驱动是工作内核空间,应用程序是工作在用户空间。如果直接用 memcpy 这些函数,可能内核空间的数据已经实效,那么复制到用户空间的则可能得到错误的信息。
写完驱动,就可进行编译,那么要写 Makefile 以方便编译。
obj-m += xxx.o
# 内核代码树位置
KDIR:=/home/alsa/kernel/linux_2.6.28
# 设置编译器
CROSS_COMPILE = arm-none-linux-gnueabi-
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
PWD=$(shell pwd)
all: build install
build: clean
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o *.ko
保存,后运行 make ,则在当前目录下生成 xxx.ko 文件。
最后通过加载驱动:
加载: insmod xxx.ko
卸载: rmmod xxx