创建一个字符设备驱动
2015-06-19 18:25
281 查看
[align=left]在前面的devfs文件系统的实验中,是使用register_chrdev函数注册的字符设备驱动。本章节通过cdev_init及cdev_add函数注册一个字符设备驱动,同时通过put_user和get_user函数实现内核和应用程序之间的简单数据交互。[/align]
[align=left]在kernel/drivers/char/x4412目录下新建x4412-cdev.c文件,编辑内容如下:[/align]
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/device.h>
static int x4412_cdev_major;
static struct cdev x4412_cdev;
static struct class *cdev_class;
int x4412_cdev_open(struct inode *inode, struct file *filp)
{
return 0;
}
int x4412_cdev_release(struct inode *inode, struct file *filp)
{
return 0;
}
static long x4412_cdev_ioctl(struct file *file,unsigned int cmd, unsigned long arg)
{
return 0;
}
static ssize_t x4412_cdev_read(struct file *filp, char __user *buf,size_t size,loff_t *ppos)
{
int read_sizes = 0;
char *p;
char Message[]="This is x4412/ibox devboard.";
p = Message;
while(size && *p)
{
if(put_user(*(p++),buf++))
return -EINVAL;
size--;
read_sizes++;
}
return read_sizes;
}
static ssize_t x4412_cdev_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)
{
int i;
char str;
for(i=0;i<size;i++)
{
if(!get_user(str,buf++))
printk("%c",str);
}
printk("\r\n");
return size;
}
static const struct file_operations x4412_fops =
{
.owner = THIS_MODULE,
.read = x4412_cdev_read,
.write = x4412_cdev_write,
.unlocked_ioctl = x4412_cdev_ioctl,
.open = x4412_cdev_open,
.release = x4412_cdev_release,
};
static void x4412_setup_cdev(void)
{
int err, devno = MKDEV(x4412_cdev_major, 0);
cdev_init(&x4412_cdev, &x4412_fops);
err = cdev_add(&x4412_cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding x4412_cdev", err);
}
static void x4412_clear_cdev(void)
{
cdev_del(&x4412_cdev);
unregister_chrdev_region(MKDEV(x4412_cdev_major, 0), 1);
}
int x4412_cdev_init(void)
{
int result;
dev_t devno;
result = alloc_chrdev_region(&devno, 0, 1, "x4412-cdev");
x4412_cdev_major = MAJOR(devno);
if(result < 0)
{
printk("register x4412-cdev error!\r\n");
return result;
}
printk("x4412-cdev main dev number:%d\r\n",x4412_cdev_major);
x4412_setup_cdev();
cdev_class = class_create(THIS_MODULE,"x4412-cdev");
if(IS_ERR(cdev_class))
{
x4412_clear_cdev();
return PTR_ERR(cdev_class);
}
device_create(cdev_class,NULL,MKDEV(x4412_cdev_major,0),NULL,"x4412-cdev");
return 0;
}
void x4412_cdev_exit(void)
{
device_destroy(cdev_class,MKDEV(x4412_cdev_major,0));
class_destroy(cdev_class);
x4412_clear_cdev();
}
MODULE_AUTHOR("www.9tripod.com");
MODULE_LICENSE("GPL");
module_init(x4412_cdev_init);
module_exit(x4412_cdev_exit);
复制代码
[align=left] 在模块初始化函数x4412_cdev_init中,通过alloc_chrdev_region函数自动分配设备号,再通过MAJOR函数将主设备号保存到全局变量x4412_cdev_major中,供其他函数调用。紧接着在x4412_setup_cdev函数中,通过MKDEV函数获取设备号,cdev_init和cdev_add函数实现file_operations相关函数的绑定和字符设备驱动的注册。class_create和device_create函数用于创建设备节点。[/align]
[align=left] 驱动的框架搭建完成后,开始给file_operations成员封装必要的函数。本实验需要通过get_user和put_user函数实现内核和应用程序的数据交互,可以通过read和write函数实现。read函数对应x4412_cdev_read,函数中定义了一个字符串数组Message,同时定义了一个指针指向该字符串,然后通过put_user函数将这个字符串传送给上层应用。write函数对应x4412_cdev_write,函数中定义了一个字符变量str,通过get_user函数从上层应用读取一串字符并打印出来。file_operations结构体的其他几个成员都直接返回0,不做任何操作。[/align]
[align=left] 在模块卸载函数中,device_destroy和class_destroy函数用于卸载设备节点和/sysfs目录下生成的相关目录,cdev_del函数用于注销设备驱动,unregister_chrdev_region函数用于注销设备号。注意,这几个函数在卸载函数中的先后顺序千万不要颠倒,否则将会崩溃。[/align]
[align=left] 在kernel/drivers/char/x4412/Kconfig中添加如下语句:[/align]
config X4412_CDEV_DRIVER
tristate "x4412 cdev driver"
default m
help
compile for x4412 cdev driver,y for kernel,m for module.
复制代码
[align=left] 在kernel/drivers/char/x4412/Makefile中添加如下语句:[/align]
obj-$(CONFIG_X4412_CDEV_DRIVER) += x4412-cdev.o
复制代码
[align=left] 编译内核,在kernel/drivers/char/x4412目录下将会生成驱动模块x4412-cdev.ko文件。[/align]
[align=left] 在ubuntu的用户目录或samba目录下新建应用程序x4412-cdev-app.c,编辑内容如下:[/align]
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define DEVICE_NAME "/dev/x4412-cdev"
int main(int argc,char **argv)
{
int fd;
char buf[30];
char str[]="hello,x4412!";
fd = open(DEVICE_NAME,O_RDWR);
if(fd == -1)
{
printf("open device %s error \n",DEVICE_NAME);
}
else
{
read(fd,buf,30);
printf("read from kernel: %s\r\n",buf);
usleep(5000);
printf("write value to kernel:\r\n");
usleep(5000);
write(fd,str,12);
close(fd);
}
return 0;
}
复制代码
[align=left] 应用程序通过open函数打开驱动后,调用read函数从驱动中读取数据并打印出来,然后将字符串数组str的内容通过write函数写到驱动中,在驱动中将应用写入的字符串打印出来。[/align]
[align=left] 执行如下指令编译应用程序:[/align]
arm-none-linux-gnueabi-gcc x4412-cdev-app.c -o x4412-cdev-app
复制代码
[align=left] 将映像文件x4412-cdev.ko及x4412-cdev-app拷贝到开发板,加载驱动测试:[/align]
[root@x4412 mnt]# cd /sys/devices/virtual/
[root@x4412 virtual]# ls
android_usb/ input/ net/ sound/ vc/
bdi/ mali/ ppp/ switch/ video4linux/
block/ mem/ rc/ tty/ vtconsole/
graphics/ misc/ regulator/ ump/
[root@x4412 virtual]# insmod /mnt/x4412-cdev.ko
[ 5602.304684] x4412-cdev main dev number:249
[root@x4412 virtual]# ls
android_usb/ input/ net/ sound/ vc/
bdi/ mali/ ppp/ switch/ video4linux/
block/ mem/ rc/ tty/ vtconsole/
graphics/ misc/ regulator/ ump/ x4412-cdev/
[root@x4412 virtual]#
复制代码
[align=left] 可以看出,加载驱动后,在/sys/devices/virtual目录下生成了x4412-cdev目录。[/align]
[root@x4412 x4412-cdev]# more /proc/devices |grep x4412
249 x4412-cdev
[root@x4412 x4412-cdev]#
复制代码
[align=left] 这里表明设备驱动的主设备号为249。[/align]
[root@x4412 x4412-cdev]# ls /dev/x4412-cdev -la
crw-rw---- 1 root root 249, 0 Oct 3 09:47 /dev/x4412-cdev
[root@x4412 x4412-cdev]#
复制代码
[align=left] 查看/dev下的设备节点时,有可能并不存在,执行mdev –s指令将会触发节点的生成。前面表明设备驱动的主设备号为249,次设备号为0。[/align]
[align=left] 执行应用程序,观察现象是否和我们期望的一致:[/align]
[root@x4412 mnt]# ./x4412-cdev-app
read from kernel: This is x4412/ibox devboard.
write value to kernel:
[ 5858.796557] hello,x4412!
[root@x4412 mnt]#
复制代码
很明显,无论是应用程序还是驱动程序,都是按照我们的要求有条不紊的执行。再来分析cdev_init,cdev_add及register_chrdev函数的区别。事实上,使用register_chrdev函数要居多,因为它用起来更加方便。事实上,register_chrdev函数的本质就是将cdev_alloc和cdev_add函数包装起来了,而cdev_alloc函数就相当于cdev_init函数,在kernel/fs/char_dev.c中可以看到register_chrdev的函数原型__register_chrdev():
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;
cd = __register_chrdev_region(major, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
return err;
}
复制代码
[align=left]在kernel/drivers/char/x4412目录下新建x4412-cdev.c文件,编辑内容如下:[/align]
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/device.h>
static int x4412_cdev_major;
static struct cdev x4412_cdev;
static struct class *cdev_class;
int x4412_cdev_open(struct inode *inode, struct file *filp)
{
return 0;
}
int x4412_cdev_release(struct inode *inode, struct file *filp)
{
return 0;
}
static long x4412_cdev_ioctl(struct file *file,unsigned int cmd, unsigned long arg)
{
return 0;
}
static ssize_t x4412_cdev_read(struct file *filp, char __user *buf,size_t size,loff_t *ppos)
{
int read_sizes = 0;
char *p;
char Message[]="This is x4412/ibox devboard.";
p = Message;
while(size && *p)
{
if(put_user(*(p++),buf++))
return -EINVAL;
size--;
read_sizes++;
}
return read_sizes;
}
static ssize_t x4412_cdev_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)
{
int i;
char str;
for(i=0;i<size;i++)
{
if(!get_user(str,buf++))
printk("%c",str);
}
printk("\r\n");
return size;
}
static const struct file_operations x4412_fops =
{
.owner = THIS_MODULE,
.read = x4412_cdev_read,
.write = x4412_cdev_write,
.unlocked_ioctl = x4412_cdev_ioctl,
.open = x4412_cdev_open,
.release = x4412_cdev_release,
};
static void x4412_setup_cdev(void)
{
int err, devno = MKDEV(x4412_cdev_major, 0);
cdev_init(&x4412_cdev, &x4412_fops);
err = cdev_add(&x4412_cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding x4412_cdev", err);
}
static void x4412_clear_cdev(void)
{
cdev_del(&x4412_cdev);
unregister_chrdev_region(MKDEV(x4412_cdev_major, 0), 1);
}
int x4412_cdev_init(void)
{
int result;
dev_t devno;
result = alloc_chrdev_region(&devno, 0, 1, "x4412-cdev");
x4412_cdev_major = MAJOR(devno);
if(result < 0)
{
printk("register x4412-cdev error!\r\n");
return result;
}
printk("x4412-cdev main dev number:%d\r\n",x4412_cdev_major);
x4412_setup_cdev();
cdev_class = class_create(THIS_MODULE,"x4412-cdev");
if(IS_ERR(cdev_class))
{
x4412_clear_cdev();
return PTR_ERR(cdev_class);
}
device_create(cdev_class,NULL,MKDEV(x4412_cdev_major,0),NULL,"x4412-cdev");
return 0;
}
void x4412_cdev_exit(void)
{
device_destroy(cdev_class,MKDEV(x4412_cdev_major,0));
class_destroy(cdev_class);
x4412_clear_cdev();
}
MODULE_AUTHOR("www.9tripod.com");
MODULE_LICENSE("GPL");
module_init(x4412_cdev_init);
module_exit(x4412_cdev_exit);
复制代码
[align=left] 在模块初始化函数x4412_cdev_init中,通过alloc_chrdev_region函数自动分配设备号,再通过MAJOR函数将主设备号保存到全局变量x4412_cdev_major中,供其他函数调用。紧接着在x4412_setup_cdev函数中,通过MKDEV函数获取设备号,cdev_init和cdev_add函数实现file_operations相关函数的绑定和字符设备驱动的注册。class_create和device_create函数用于创建设备节点。[/align]
[align=left] 驱动的框架搭建完成后,开始给file_operations成员封装必要的函数。本实验需要通过get_user和put_user函数实现内核和应用程序的数据交互,可以通过read和write函数实现。read函数对应x4412_cdev_read,函数中定义了一个字符串数组Message,同时定义了一个指针指向该字符串,然后通过put_user函数将这个字符串传送给上层应用。write函数对应x4412_cdev_write,函数中定义了一个字符变量str,通过get_user函数从上层应用读取一串字符并打印出来。file_operations结构体的其他几个成员都直接返回0,不做任何操作。[/align]
[align=left] 在模块卸载函数中,device_destroy和class_destroy函数用于卸载设备节点和/sysfs目录下生成的相关目录,cdev_del函数用于注销设备驱动,unregister_chrdev_region函数用于注销设备号。注意,这几个函数在卸载函数中的先后顺序千万不要颠倒,否则将会崩溃。[/align]
[align=left] 在kernel/drivers/char/x4412/Kconfig中添加如下语句:[/align]
config X4412_CDEV_DRIVER
tristate "x4412 cdev driver"
default m
help
compile for x4412 cdev driver,y for kernel,m for module.
复制代码
[align=left] 在kernel/drivers/char/x4412/Makefile中添加如下语句:[/align]
obj-$(CONFIG_X4412_CDEV_DRIVER) += x4412-cdev.o
复制代码
[align=left] 编译内核,在kernel/drivers/char/x4412目录下将会生成驱动模块x4412-cdev.ko文件。[/align]
[align=left] 在ubuntu的用户目录或samba目录下新建应用程序x4412-cdev-app.c,编辑内容如下:[/align]
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define DEVICE_NAME "/dev/x4412-cdev"
int main(int argc,char **argv)
{
int fd;
char buf[30];
char str[]="hello,x4412!";
fd = open(DEVICE_NAME,O_RDWR);
if(fd == -1)
{
printf("open device %s error \n",DEVICE_NAME);
}
else
{
read(fd,buf,30);
printf("read from kernel: %s\r\n",buf);
usleep(5000);
printf("write value to kernel:\r\n");
usleep(5000);
write(fd,str,12);
close(fd);
}
return 0;
}
复制代码
[align=left] 应用程序通过open函数打开驱动后,调用read函数从驱动中读取数据并打印出来,然后将字符串数组str的内容通过write函数写到驱动中,在驱动中将应用写入的字符串打印出来。[/align]
[align=left] 执行如下指令编译应用程序:[/align]
arm-none-linux-gnueabi-gcc x4412-cdev-app.c -o x4412-cdev-app
复制代码
[align=left] 将映像文件x4412-cdev.ko及x4412-cdev-app拷贝到开发板,加载驱动测试:[/align]
[root@x4412 mnt]# cd /sys/devices/virtual/
[root@x4412 virtual]# ls
android_usb/ input/ net/ sound/ vc/
bdi/ mali/ ppp/ switch/ video4linux/
block/ mem/ rc/ tty/ vtconsole/
graphics/ misc/ regulator/ ump/
[root@x4412 virtual]# insmod /mnt/x4412-cdev.ko
[ 5602.304684] x4412-cdev main dev number:249
[root@x4412 virtual]# ls
android_usb/ input/ net/ sound/ vc/
bdi/ mali/ ppp/ switch/ video4linux/
block/ mem/ rc/ tty/ vtconsole/
graphics/ misc/ regulator/ ump/ x4412-cdev/
[root@x4412 virtual]#
复制代码
[align=left] 可以看出,加载驱动后,在/sys/devices/virtual目录下生成了x4412-cdev目录。[/align]
[root@x4412 x4412-cdev]# more /proc/devices |grep x4412
249 x4412-cdev
[root@x4412 x4412-cdev]#
复制代码
[align=left] 这里表明设备驱动的主设备号为249。[/align]
[root@x4412 x4412-cdev]# ls /dev/x4412-cdev -la
crw-rw---- 1 root root 249, 0 Oct 3 09:47 /dev/x4412-cdev
[root@x4412 x4412-cdev]#
复制代码
[align=left] 查看/dev下的设备节点时,有可能并不存在,执行mdev –s指令将会触发节点的生成。前面表明设备驱动的主设备号为249,次设备号为0。[/align]
[align=left] 执行应用程序,观察现象是否和我们期望的一致:[/align]
[root@x4412 mnt]# ./x4412-cdev-app
read from kernel: This is x4412/ibox devboard.
write value to kernel:
[ 5858.796557] hello,x4412!
[root@x4412 mnt]#
复制代码
很明显,无论是应用程序还是驱动程序,都是按照我们的要求有条不紊的执行。再来分析cdev_init,cdev_add及register_chrdev函数的区别。事实上,使用register_chrdev函数要居多,因为它用起来更加方便。事实上,register_chrdev函数的本质就是将cdev_alloc和cdev_add函数包装起来了,而cdev_alloc函数就相当于cdev_init函数,在kernel/fs/char_dev.c中可以看到register_chrdev的函数原型__register_chrdev():
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;
cd = __register_chrdev_region(major, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
return err;
}
复制代码
相关文章推荐
- PHP常用函数总结
- 林达华推荐的几本数学书
- 大海捞枕木:大数据处理平台的衍变
- Markdonw 小技巧
- 只要游戏没有结束,你就有成为神的可能
- php面向对象基本概念(魔法方法)__invoke() __toString() __call() __callStatic() __clone()
- leetcode--RecoverBinarySearchTree
- Error Code: 2006 - MySQL server has gone away
- 京东夺宝岛的分析
- 如何读懂一篇学术论文
- jQuery页面加载初始化的3种方法
- 遍历网格
- linux安装cmake
- 祝贺自己itpub和csdn双双荣获专家博客标题
- Android事件机制之一:事件传递和消费
- 如何在一台机器上配置多个git的rsa
- TCP/IP学习笔记(3)-----------TCP/IP协议详解
- [原创]CentOS下Radius服务器搭建
- 用nginx-gridFS读取MongoDB的图片及文件(为什么你老是配不成功?)
- 用nginx-gridFS读取MongoDB的图片及文件(为什么你老是配不成功?)