在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