字符设备驱动开发

在Linux系统中,用户空间和内核空间之间是相互隔离开的。驱动程序运行在内核空间中,给出的地址也是在内核空间中的地址,运行在用户空间下的用户程序即使拿到这个地址,也不能访问内核空间。这时,我们需要使用到copy_to_user()函数,将要传递的内容从内核空间拷贝到用户空间,用户程序再访问用户空间中的该内容即可。

copy_to_user函数的原型如下。

unsigned long copy_to_user(void *to, const void *from, unsigned long n)

1

参数含义:

1)to:目标地址(用户空间)

2)from:源地址(内核空间)

3)n:需要拷贝的数据的字节数

返回值:成功返回0,失败返回没有拷贝成功的数据字节数。

同理,也有从用户空间向内核空间拷贝的函数copy_from_user(),原型如下。

unsigned long copy_from_user(void *to, const void *from, unsigned long n)

1

参数含义:

1)to:目标地址(内核空间)

2)from:源地址(用户空间)

3)n:需要拷贝的数据的字节数

返回值:成功返回0,失败返回没有拷贝成功的数据字节数。

下面举个实例,来详细介绍如何在用户空间和内核空间中通过传递地址参数的方法来传递复杂参数。这里我们传递两个参数char arg1和int arg2,将这两个参数打包进一个结构体struct IOC_ARGS中。这个结构体在用户程序和驱动程序中也需要保持一致。

同样我们还需要定义用户程序和驱动程序之间命令码,我们定义两个命令码,分别用来读参数和写参数。

用户程序的头文件user_ioctl.h。

#include <sys/ioctl.h>

struct IOC_ARGS {

char arg1;

int arg2;

};

#define CMD_IOC_MAGIC 'a'

#define CMD_IOC_0 _IOR(CMD_IOC_MAGIC, 0, struct IOC_ARGS)

#define CMD_IOC_1 _IOW(CMD_IOC_MAGIC, 1, struct IOC_ARGS)

内核程序的头文件ioctl_test.h。

#include <linux/ioctl.h>

typedef struct IOC_ARGS {

char arg1;

int arg2;

}IOC_ARGS;

#define CMD_IOC_MAGIC 'a'

#define CMD_IOC_0 _IOR(CMD_IOC_MAGIC, 0, struct IOC_ARGS)

#define CMD_IOC_1 _IOW(CMD_IOC_MAGIC, 1, struct IOC_ARGS)

用户程序user_ioctl.c。

#include <stdio.h>

#include <fcntl.h>

#include <unistd.h>

#include <errno.h>

#include <string.h>

#include "user_ioctl.h"

int main()

{

int rc;

struct IOC_ARGS args_r;

struct IOC_ARGS args_w = {'u', 233};

int fd = open("/dev/test_chr_dev", O_RDWR);

rc = ioctl(fd, CMD_IOC_0, &args_r);

if (rc < 0)

printf("ioctl: %s\n", strerror(errno));

else

printf("ioc read arg1 = %c, arg2 = %d.\n", args_r.arg1, args_r.arg2);

rc = ioctl(fd, CMD_IOC_1, &args_w);

if (rc < 0)

printf("ioctl: %s\n", strerror(errno));

else

printf("ioc write arg1 = %c, arg2 = %d.\n", args_w.arg1, args_w.arg2);

close(fd);

return 0;

}

内核驱动程序ioctl_test.c。

#include <linux/init.h>

#include <linux/module.h>

#include <linux/fs.h>

#include <linux/cdev.h>

#include <linux/uaccess.h>

#include "ioctl_test.h"

MODULE_LICENSE("GPL");

MODULE_AUTHOR("zz");

static dev_t devno;

static int demo_open(struct inode *ind, struct file *fp)

{

printk("demo open\n");

return 0;

}

static int demo_release(struct inode *ind, struct file *fp)

{

printk("demo release\n");

return 0;

}

static long demo_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)

{

int rc = 0;

struct IOC_ARGS args_r = {'k', 566};

struct IOC_ARGS args_w;

if (_IOC_TYPE(cmd) != CMD_IOC_MAGIC) {

pr_err("%s: command type [%c] error.\n", func, _IOC_TYPE(cmd));

return -ENOTTY;

}

switch(cmd) {

case CMD_IOC_0:

rc = copy_to_user((char __user *)arg, &args_r, sizeof(IOC_ARGS));

if (rc) {

pr_err("%s: copy_to_user failed", func);

return rc;

}

printk("%s: ioc read arg1 = %c, arg2 = %d", func, args_r.arg1, args_r.arg2);

break;

break;

case CMD_IOC_1:

rc = copy_from_user(&args_w, (char __user *)arg, sizeof(IOC_ARGS));

if (rc) {

pr_err("%s: copy_from_user failed", func);

return rc;

}

printk("%s: ioc write arg1 = %c, arg2 = %d", func, args_w.arg1, args_w.arg2);

break;

default:

pr_err("%s: invalid command.\n", func);

return -ENOTTY;

}

return rc;

}

static struct file_operations fops = {

.open = demo_open,

.release = demo_release,

.unlocked_ioctl = demo_ioctl,

};

static struct cdev cd;

static int demo_init(void)

{

int rc;

rc = alloc_chrdev_region(&devno, 0, 1, "test");

if(rc < 0) {

pr_err("alloc_chrdev_region failed!");

return rc;

}

printk("MAJOR is %d\n", MAJOR(devno));

printk("MINOR is %d\n", MINOR(devno));

cdev_init(&cd, &fops);

rc = cdev_add(&cd, devno, 1);

if (rc < 0) {

pr_err("cdev_add failed!");

return rc;

}

return 0;

}

static void demo_exit(void)

{

cdev_del(&cd);

unregister_chrdev_region(devno, 1);

return;

}

module_init(demo_init);

module_exit(demo_exit);

Makefile 文件:

ifneq ($(KERNELRELEASE),)

obj-m := ioctl_test.o

else

KDIR := /lib/modules/$(shell uname -r)/build

PWD := $(shell pwd)

all:

make -C (KDIR) M=(PWD) modules

gcc user_ioctl.c -o user

clean:

make -C (KDIR) M=(PWD) clean

rm -rf user

endif

运行结果。首先编译代码。

进入root模式,安装模块,打印内核信息。

注册设备节点,运行用户程序。

打印内核输出。

删除设备节点,移除模块,结束。

相关推荐
cxr8281 天前
SPARC方法论在Claude Code基于规则驱动开发中的应用
人工智能·驱动开发·claude·智能体
sukalot1 天前
window显示驱动开发—显示适配器的子设备
驱动开发
Evan_ZGYF丶2 天前
【RK3576】【Android14】如何在Android14下单独编译kernel-6.1?
linux·驱动开发·android14·rk3576
sukalot3 天前
window显示驱动开发—视频呈现网络简介
驱动开发
sukalot3 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(二)
驱动开发
zwhSunday3 天前
Linux驱动开发(1)概念、环境与代码框架
linux·运维·驱动开发
sukalot4 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(三)
驱动开发
sukalot4 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(一)
驱动开发
cxr8285 天前
基于Claude Code的 规范驱动开发(SDD)指南
人工智能·hive·驱动开发·敏捷流程·智能体
zwhSunday5 天前
Linux驱动开发(2)进一步理解驱动
linux·驱动开发