您的位置:首页 > 运维架构 > Linux

linux内核字符设备驱动之写操作

2017-09-15 15:46 471 查看

应用程序write函数的使用:

char *p = “hello,world”;

write(fd, p, 12); //将数据写入到设备

底层驱动write接口

struct file_operations {
ssize_t (*write) (struct file *file,
const char __user *buf,
size_t count,
loff_t *ppos);
};
write接口作用:用于写设备,将数据写入到设备中
与应用程序write的调用关系:
应用程序调用write->...->调用驱动write接口
参数:
file:文件指针
buf:保存用户缓冲区的首地址(p),在驱动程序中不能直接访问这个buf,如果驱动程序要向从用户空间将数据从buf拷贝到内核空间,必须利用内核提供的内存拷贝函数
count:用户要写入的字节数,例如12字节
ppos:保存写的位置信息,例如
获取上一次的写位置:
loff_t pos = *ppos;
假如这次成功写了12字节;
最后要更新写位置信息:
*ppos = pos + 12;


切记:对于write接口的第二个参数buf,这个buf指针保存的是用户缓冲区的首地址,在内核空间不能直接访问操作,需要利用内核的内存拷贝函数,将用户数据拷贝到内核空间,这个内存拷贝函数:

unsigned long copy_from_user(void  *to,
void __user *from,
unsigned long n)
作用:将用户缓冲区的数据拷贝到内核缓冲区中
参数:
to:目的地址,传递内核缓冲区的首地址
from:源地址,传递用户缓冲区的首地址(buf)
n:要拷贝的字节数
将来只要看到__user修饰的指针,就不能在驱动中直接访问操作,必须利用内存拷贝函数!


案例:编写字符设备驱动,提供write接口,将用户空间的数据写入到内核空间

int udata = 0x5555;

write(fd, &udata, sizeof(udata));

#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

//声明描述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;

//调用关系:应用程序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)
{
//定义内核缓冲区
int kdata;

//拷贝用户数据到内核
copy_from_user(&kdata, buf, sizeof(kdata));
printk("%s:从用户写入的数据 kdata = %#x\n", __func__, kdata);
return count; //失败返回负值,成功返回写入的字节数
}

//定义初始化硬件操作方法
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open, //打开设备
.release = led_close, //关闭设备
.read = led_read, //读取设备
.write = led_write //写设备
};

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);
}
return 0;
}

static void led_exit(void)
{
int i;

//输出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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
int fd;
int udata = 0x5555;  //定义用户缓冲区

//打开设备
//open->....->调用led_open
fd = open("/dev/myled", O_RDWR);
if (fd < 0) {
printf("打开设备失败!\n");
return -1;
}

//write->...->调用led_write
write(fd, &udata, sizeof(udata));

//关闭设备
//close->...->调用led_close
close(fd);
return 0;
}


案例:用户写1,开所有的灯;用户写0,关所有的灯;

#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

//声明描述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;

//调用关系:应用程序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)
{
int i;

//定义内核缓冲区
int kdata;

//拷贝用户数据到内核
copy_from_user(&kdata, buf, sizeof(kdata));

//开或者关灯
for (i = 0; i < ARRAY_SIZE(led_info); i++)
gpio_set_value(led_info[i].gpio, kdata);

return count; //失败返回负值,成功返回写入的字节数
}

//定义初始化硬件操作方法
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open, //打开设备
.release = led_close, //关闭设备
.read = led_read, //读取设备
.write = led_write //写设备
};

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);
}
return 0;
}

static void led_exit(void)
{
int i;

//输出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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
int fd;
int udata;  //定义用户缓冲区

//打开设备
//open->....->调用led_open
fd = open("/dev/myled", O_RDWR);
if (fd < 0) {
printf("打开设备失败!\n");
return -1;
}

//write->...->调用led_write
while (1) {
udata = 1; //开
write(fd, &udata, sizeof(udata));
sleep(1);
udata = 0; //关
write(fd, &udata, sizeof(udata));
sleep(1);
}
//关闭设备
//close->...->调用led_close
close(fd);
return 0;
}


案例:用户能够指定其中某个灯的开关状态;

提示:

用户不仅仅要告诉灯的开关状态,还要告诉驱动用户现在要想操作哪个灯;

#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

//声明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;

//调用关系:应用程序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; //失败返回负值,成功返回写入的字节数
}

//定义初始化硬件操作方法
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open, //打开设备
.release = led_close, //关闭设备
.read = led_read, //读取设备
.write = led_write //写设备
};

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);
}
return 0;
}

static void led_exit(void)
{
int i;

//输出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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//声明LED操作的数据结构
struct led_cmd {
int index; //指定灯的编号
int cmd; //开关命令
};

int main(void)
{
int fd;
struct led_cmd udata;  //定义用户缓冲区

//打开设备
//open->....->调用led_open
fd = open("/dev/myled", O_RDWR);
if (fd < 0) {
printf("打开设备失败!\n");
return -1;
}

//write->...->调用led_write
while (1) {
udata.index = 1; //第一个灯
udata.cmd = 1;  //开
write(fd, &udata, sizeof(udata));
sleep(1);
udata.index = 2; //第二个灯
write(fd, &udata, sizeof(udata));
sleep(1);
}
//关闭设备
//close->...->调用led_close
close(fd);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息