推荐周立功先生的书籍《嵌入式Linux开发教程(下册)》,该书籍用于学习开发是不错的参考资料。
字符设备框架
char_dev_frame.c:
a 驱动程序
#include
#include
#include
#include
#include
static int major = 0;
static int minor = 0;
module_param(major, int, S_IRUGO);
module_param(minor, int ,S_IRUGO);
struct cdev *char_cdev;
static dev_t dev_no;
static struct class *char_cdev_class;
static struct class_device *char_cdev_class_device;
#define DEVICE_NAME "char_cdev"
#define DEVICE_FILE_NAME "/dev/char_cdev"
static int char_cdev_open(struct inode *inode, struct file *file)
{
try_module_get(THIS_MODULE);
printk(KERN_INFO DEVICE_NAME"opened!
");
return 0;
}
static int char_cdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO DEVICE_NAME"closed!
");
module_put(THIS_MODULE);
return 0;
}
static ssize_t char_cdev_read(struct file *file, char *buf, size_t count, loff_t *f_ops)
{
printk(KERN_INFO DEVICE_NAME"read method!
");
return count;
}
static ssize_t char_cdev_write(struct file *file, const char *buf, size_t count, loff_t *f_ops)
{
printk(KERN_INFO DEVICE_NAME"write method!
");
return count;
}
static int char_cdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
printk(KERN_INFO DEVICE_NAME"ioctl method!
");
return 0;
}
struct file_operations char_cdev_fops = {
.owner = THIS_MODULE,
.read = char_cdev_read,
.write = char_cdev_write,
.ioctl = char_cdev_ioctl,
.open = char_cdev_open,
.release = char_cdev_release
};
static int __init char_cdev_init(void)
{
int ret;
if(major > 0) {
dev_no = MKDEV(major, minor);
ret = register_chrdev_region(dev_no, 1, DEVICE_NAME);
} else {
ret = alloc_chrdev_region(&dev_no, minor, 1, DEVICE_NAME);
major = MAJOR(dev_no);
}
if(ret < 0) {
printk(KERN_ERR"can't get major %d
", major);
return -1;
}
char_cdev = cdev_alloc();
if(char_cdev != NULL) {
cdev_init(char_cdev, &char_cdev_fops);
char_cdev->owner = THIS_MODULE;
if(cdev_add(char_cdev, dev_no, 1) != 0) {
printk(KERN_ERR"add cdev error!");
goto error;
}
} else {
printk(KERN_ERR"cdev_alloc_error!
");
return -1;
}
char_cdev_class = class_create(THIS_MODULE, "char_cdev_class");
if(IS_ERR(char_cdev_class)) {
printk(KERN_INFO"create class error
");
return -1;
}
char_cdev_class_device = class_device_create(char_cdev_class, NULL, dev_no, NULL, "char_cdev");
return 0;
error:
unregister_chrdev_region(dev_no, 1);
return ret;
}
static void __exit char_cdev_exit(void)
{
cdev_del(char_cdev);
unregister_chrdev_region(dev_no, 1);
class_device_unregister(char_cdev_class_device);
class_destroy(char_cdev_class);
}
module_init(char_cdev_init);
module_exit(char_cdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("huchunrong");
添加设备节点时,因为kernel版本为2.6.22.6
,应使用class_device_create
,否则一直提示警告。后期的某个版本开始,class_device_create
被device_create
替代。关于这两个函数的分析,网上分析很多。
b 驱动程序Makefile
# Makefile2.6
ifneq ($(KERNELRELEASE),)
# kbuild syntax. dependency relationshsip of files and target modules are listed here.
obj-m := char_dev_frame.o
else
PWD := $(shell pwd)
KVER := 2.6.22.6
KDIR := /home/eva/Developer/Linux/kernel/linux-2.6.22.6
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
endif
上面的Makefile为Linux2.6内核下一段比较通用的Makefile。
aKERNELRELEASE
是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE
没有被定义,所以make将读取执行else之后的内容;
b 当make的目标为all时,-C $(KDIR)
跳转到内核源码目录下读取Makefile;M=$(PWD) 然后返回到当前目录继续读入、执行当前的Makefile。
c 测试程序
char_dev_test.c:
#include
#include
#include
#include
#include
#include
#define DEVICE_FILE_NAME "/dev/char_cdev"
int main(int argc, char *argv[])
{
int fd, ret;
char data;
fd = open(DEVICE_FILE_NAME, O_RDWR);
if(fd < 0) {
perror("Open "DEVICE_FILE_NAME" Failed
");
exit(1);
}
ret = read(fd, &data, 1);
if(!ret) {
perror("Read "DEVICE_FILE_NAME" Failed
");
exit(1);
}
data = 1;
ret = write(fd, &data, 1);
if(!ret) {
perror("Write "DEVICE_FILE_NAME" Failed
");
exit(1);
}
ret = ioctl(fd, 0, NULL);
if(ret) {
perror("Ioctl "DEVICE_FILE_NAME" Failed
");
exit(1);
}
close(fd);
return 0;
}
编译
a 编译驱动
因为事先已经写好了Makefile,直接执行make
命令即可
make
b 编译测试程序
arm-linux-gcc char_dev_test.c -o char_dev_test.o
加载和运行
拷贝可执行文件到nfs共享目录:cp char_dev_frame.ko char_dev_test.o ../../../bin/
在嵌入式设备上挂载nfs共享目录:mount -t nfs -o intr,nolock,rsize=1024,wsize=1024 192.168.1.12:/home/eva/Developer/Linux/bin /mnt
当文件比较大时,使用该指令成功率比较高
安装驱动程序:insmod char_dev_frame.ko
执行测试程序:./char_dev_test.o
如果驱动程序里没有进行创建设备文件,即没有执行class_device_create
,也可以根据主设备号和次设备号,手动创建字符设备节点mknod /dev/char_cdev c 252 0
查看当前系统支持的设备cat /proc/devices
查看设备节点ls /dev
警告及错误
现象描述:
编译时在read
和write
函数附近,提示warning: initialization from incompatible pointer type
,一般原因为数据类型不匹配
解决措施:
定义read
函数时,第三个参数类型为size_t
,而不是ssize_t,改正后如下:static ssize_t char_cdev_read(struct file *file, char *buf, ssize_t count, loff_t *f_ops)
,write
函数如此
现象描述:
编译时device_create
函数提示警告warning: too many arguments for format
,原因为jz2440的内核版本linux2.6.22.6,还不支持device_create
函数
解决措施:
改用class_device_create
函数,完整示例如下char_cdev_class_device = class_device_create(char_cdev_class, NULL, dev_no, NULL, "char_cdev");
现象描述:
insmod
安装驱动后,无法在/dev/
路径下找到设备节点,只能手动创建设备文件节点,但驱动程序里已经正确调用class_device_create
函数
解决措施:
和烧写的文件系统有关,烧写fs_mini_mdev.yaffs2
文件系统,而不是 fs_mini.yaffs2
,具体原因未知