1. linux内核字符设备驱动之设备文件的自动创建
设备文件创建:
手动创建:
mknod /dev/设备文件名 c/b 主设备号 次设备号
自动创建:
作用:每当加载完设备驱动以后,设备文件也会创建成功!
每当卸载驱动以后,设备文件也会自动删除;
设备文件自动创建的编程步骤:
1.根文件系统要支持 mdev可执行程序
mdev的作用就是将来创建设备文件;
mdev是udev的一个简化版本,适用嵌入式系统;
which is mdev //查看mdev在哪个目录下
2.根文件系统的etc目录下要有fstab文件,此文件内 容必须添加以下三句话:
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
说明:
tmpfs,sysfs,procfs是三种虚拟文件系统,那么对应的入口分别是/dev目录,/sys目录,/proc目录,三个目录下的内容都是存在内存上;
/dev/目录将来存放创建的设备文件
/sys目录将来存放创建设备文件所需的原材料:主设备号,次设备号,设备文件名字
/proc目录存放 hotplug配置文件,给驱动使用
3.根文件系统的启动脚本etc/init.d/rcS文件中必须 添加以下句话:
mount -a #每当执行这句话时,fstab将会被使用
echo /sbin/mdev > /proc/sys/kernel/hotplug #告诉内核和驱动将来创建设备文件的人是mdev
mdev -s #系统启动完毕,将系统默认支持的设备的设备文件自动创建完毕
4.设备驱动只需利用以下四个函数即可完成设备文件的最终创建
struct class *cls; //创建设备类指针
//定义初始化设备类(长树枝,树枝名叫tarena)
//结果是在/sys/class目录下生成一个tarena目录,存放创建设备文件所需的原材料
cls = class_create(THIS_MODULE, "tarena");
//创建设备文件(长苹果)
//结果是在/dev/生成设备文件
device_create(cls, NULL, 申请的设备号,NULL,设备文件名);
//删除设备文件(采摘苹果)
device_destroy(cls, 申请的设备号);
//删除设备类(砍树枝)
class_destroy(cls);
案例:在以往的字符设备驱动中添加设备文件的自动创建功能#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h> //struct file_operations
#include <linux/cdev.h> //struct cdev + 设备号
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/uaccess.h> //copy_to_user
#include <linux/device.h> //设备文件自动创建
//声明LED操作的数据结构
struct led_cmd {
int index;
int cmd;
};
//声明描述LED硬件相关的数据结构
struct led_resource {
char *name;
int gpio;
};
//定义初始化LED硬件信息
static struct led_resource led_info[] = {
[0] = {
.name = "LED1",
.gpio = S5PV210_GPC0(3)
},
[1] = {
.name = "LED2",
.gpio = S5PV210_GPC0(4)
}
};
//定义设备号
static dev_t dev;
//定义字符设备对象
static struct cdev led_cdev;
//定义设备类指针
static struct class *cls;
//调用关系:应用程序open->....->led_open
static int led_open(struct inode *inode, struct file *file)
{
int i;
for(i = 0; i < ARRAY_SIZE(led_info); i++)
gpio_set_value(led_info[i].gpio, 1);
printk("%s\n", __func__);
return 0; //执行成功返回0,执行失败返回负值
}
//调用关系:应用程序close->...->led_close
static int led_close(struct inode *inode, struct file *file)
{
int i;
for(i = 0; i < ARRAY_SIZE(led_info); i++)
gpio_set_value(led_info[i].gpio, 0);
printk("%s\n", __func__);
return 0; //执行成功返回0,执行失败返回负值
}
//调用关系:应用程序read->...->led_read
static ssize_t led_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
//定义初始化内核缓冲区(存储空间再后1G虚拟内存中)
int kdata = 0x5555;
//将内核数据上报给用户
//切记:buf虽然保存的用户缓冲区的首地址,但不能直接访问
//*(int *)buf = kdata;错误
copy_to_user(buf, &kdata, sizeof(kdata));
printk("%s\n", __func__);
return sizeof(kdata); //失败返回负值,成功返回实际读取的字节数
}
//调用关系:应用程序write->...->最终调用led_write
static ssize_t led_write(struct file *file,
const char __user *buf,
size_t count,
loff_t *ppos)
{
//定义内核缓冲区
struct led_cmd kdata;
//拷贝用户数据到内核
copy_from_user(&kdata, buf, sizeof(kdata));
//开或者关灯
gpio_set_value(led_info[kdata.index - 1].gpio, kdata.cmd);
printk("%s:第%d灯被%s\n",
__func__, kdata.index, kdata.cmd?"打开":"关闭");
return count; //失败返回负值,成功返回写入的字节数
}
#define LED_ON 0x100001 //开灯命令
#define LED_OFF 0x100002 //关灯命令
static long led_ioctl(struct file *file,
unsigned int cmd,
unsigned long arg)
{
//定义内核缓冲区,保存用户缓冲区的数据
int kindex;
//拷贝用户数据到内核
copy_from_user(&kindex, (int *)arg, sizeof(kindex));
//解析处理用户命令
switch(cmd) {
case LED_ON:
gpio_set_value(led_info[kindex-1].gpio, 1);
printk("%s:第%d个灯被打开!\n", __func__, kindex);
break;
case LED_OFF:
gpio_set_value(led_info[kindex-1].gpio, 0);
printk("%s:第%d个灯被关闭!\n", __func__, kindex);
break;
default:
return -1;
}
return 0; //成功返回0,失败返回-1
}
//定义初始化硬件操作方法
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open, //打开设备
.release = led_close, //关闭设备
.read = led_read, //读取设备
.write = led_write, //写设备
.unlocked_ioctl = led_ioctl //向设备发送命令和完成读写
};
static int led_init(void)
{
int i;
//申请设备号
alloc_chrdev_region(&dev, 0, 1, "tarena");
//初始化字符设备对象
cdev_init(&led_cdev, &led_fops);
//注册字符设备对象到内核
cdev_add(&led_cdev, dev, 1);
//申请GPIO资源和配置GPIO为输出口,输出0(省电)
for (i = 0; i < ARRAY_SIZE(led_info); i++) {
gpio_request(led_info[i].gpio, led_info[i].name);
gpio_direction_output(led_info[i].gpio, 0);
}
//创建设备类(长树枝)
//结果/sys/class/tarena(新创建)
cls = class_create(THIS_MODULE, "tarena");
//创建设备文件(长苹果)
//结果自动创建设备文件/dev/myled
device_create(cls, NULL, dev, NULL, "myled");
return 0;
}
static void led_exit(void)
{
int i;
//删除设备文件(采摘苹果)
device_destroy(cls, dev);
//删除设备类(砍树枝)
class_destroy(cls);
//输出0,释放GPIO资源
for (i = 0; i < ARRAY_SIZE(led_info); i++) {
gpio_set_value(led_info[i].gpio, 0);
gpio_free(led_info[i].gpio);
}
//卸载字符设备对象
cdev_del(&led_cdev);
//释放设备号
unregister_chrdev_region(dev, 1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h> //struct file_operations
#include <linux/cdev.h> //struct cdev + 设备号
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/uaccess.h> //copy_to_user
#include <linux/device.h> //设备文件自动创建
//声明LED操作的数据结构
struct led_cmd {
int index;
int cmd;
};
//声明描述LED硬件相关的数据结构
struct led_resource {
char *name;
int gpio;
};
//定义初始化LED硬件信息
static struct led_resource led_info[] = {
[0] = {
.name = "LED1",
.gpio = S5PV210_GPC0(3)
},
[1] = {
.name = "LED2",
.gpio = S5PV210_GPC0(4)
}
};
//定义设备号
static dev_t dev;
//定义字符设备对象
static struct cdev led_cdev;
//定义设备类指针
static struct class *cls;
//调用关系:应用程序open->....->led_open
static int led_open(struct inode *inode, struct file *file)
{
int i;
for(i = 0; i < ARRAY_SIZE(led_info); i++)
gpio_set_value(led_info[i].gpio, 1);
printk("%s\n", __func__);
return 0; //执行成功返回0,执行失败返回负值
}
//调用关系:应用程序close->...->led_close
static int led_close(struct inode *inode, struct file *file)
{
int i;
for(i = 0; i < ARRAY_SIZE(led_info); i++)
gpio_set_value(led_info[i].gpio, 0);
printk("%s\n", __func__);
return 0; //执行成功返回0,执行失败返回负值
}
//调用关系:应用程序read->...->led_read
static ssize_t led_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
//定义初始化内核缓冲区(存储空间再后1G虚拟内存中)
int kdata = 0x5555;
//将内核数据上报给用户
//切记:buf虽然保存的用户缓冲区的首地址,但不能直接访问
//*(int *)buf = kdata;错误
copy_to_user(buf, &kdata, sizeof(kdata));
printk("%s\n", __func__);
return sizeof(kdata); //失败返回负值,成功返回实际读取的字节数
}
//调用关系:应用程序write->...->最终调用led_write
static ssize_t led_write(struct file *file,
const char __user *buf,
size_t count,
loff_t *ppos)
{
//定义内核缓冲区
struct led_cmd kdata;
//拷贝用户数据到内核
copy_from_user(&kdata, buf, sizeof(kdata));
//开或者关灯
gpio_set_value(led_info[kdata.index - 1].gpio, kdata.cmd);
printk("%s:第%d灯被%s\n",
__func__, kdata.index, kdata.cmd?"打开":"关闭");
return count; //失败返回负值,成功返回写入的字节数
}
#define LED_ON 0x100001 //开灯命令
#define LED_OFF 0x100002 //关灯命令
static long led_ioctl(struct file *file,
unsigned int cmd,
unsigned long arg)
{
//定义内核缓冲区,保存用户缓冲区的数据
int kindex;
//拷贝用户数据到内核
copy_from_user(&kindex, (int *)arg, sizeof(kindex));
//解析处理用户命令
switch(cmd) {
case LED_ON:
gpio_set_value(led_info[kindex-1].gpio, 1);
printk("%s:第%d个灯被打开!\n", __func__, kindex);
break;
case LED_OFF:
gpio_set_value(led_info[kindex-1].gpio, 0);
printk("%s:第%d个灯被关闭!\n", __func__, kindex);
break;
default:
return -1;
}
return 0; //成功返回0,失败返回-1
}
//定义初始化硬件操作方法
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open, //打开设备
.release = led_close, //关闭设备
.read = led_read, //读取设备
.write = led_write, //写设备
.unlocked_ioctl = led_ioctl //向设备发送命令和完成读写
};
static int led_init(void)
{
int i;
//申请设备号
alloc_chrdev_region(&dev, 0, 1, "tarena");
//初始化字符设备对象
cdev_init(&led_cdev, &led_fops);
//注册字符设备对象到内核
cdev_add(&led_cdev, dev, 1);
//申请GPIO资源和配置GPIO为输出口,输出0(省电)
for (i = 0; i < ARRAY_SIZE(led_info); i++) {
gpio_request(led_info[i].gpio, led_info[i].name);
gpio_direction_output(led_info[i].gpio, 0);
}
//创建设备类(长树枝)
//结果/sys/class/tarena(新创建)
cls = class_create(THIS_MODULE, "tarena");
//创建设备文件(长苹果)
//结果自动创建设备文件/dev/myled
device_create(cls, NULL, dev, NULL, "myled");
return 0;
}
static void led_exit(void)
{
int i;
//删除设备文件(采摘苹果)
device_destroy(cls, dev);
//删除设备类(砍树枝)
class_destroy(cls);
//输出0,释放GPIO资源
for (i = 0; i < ARRAY_SIZE(led_info); i++) {
gpio_set_value(led_info[i].gpio, 0);
gpio_free(led_info[i].gpio);
}
//卸载字符设备对象
cdev_del(&led_cdev);
//释放设备号
unregister_chrdev_region(dev, 1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");