07、用户空间和内核空间数据交换
内核空间和用户空间的内存是不能互相访问的。 但是很多应用程序都需要和内核进行数据的交换, 例如应用程序使用 read 函数从驱动中读取数据, 使用 write 函数向驱动中写数据, 上述功能就需要使用 copy_from_user 和 copy_to_user 俩个函数来完成。 copy_from_user 函数是将用户空间的数据拷贝到内核空间。 copy_to_user 函数是将内核空间的数据拷贝到用户空间。
这俩个函数定义在了 kernel/include/linux/uaccess.h 文件下, 如下所示:
一、API
1.1、copy_to_user
函数定义: copy_to_user() 函数用于将内核中的数据复制到用户程序的内存区域。
参数说明:
to
:目标地址,是应用程序内存中的一个位置。from
:源地址,是内核内存中需要拷贝的数据位置。n
:要拷贝的数据量,单位是字节。
简单总结: 这个函数就像一个搬运工,把内核里的数据(比如文件内容或计算结果)搬去用户程序能用的地方,并指定搬多少数据。
1.2、copy_from_user
函数定义:
将用户程序中的数据复制到操作系统内核的内存区域。
参数说明:
- to: 内核内存的地址,数据会被复制到这里。
- from: 用户程序内存的地址,数据来源。
- n: 需要复制的数据大小(单位为字节)。
简单总结:
这个函数的作用就像一个搬运工,把用户程序(比如你运行的软件)里的数据,安全地搬移到操作系统内核(计算机的核心程序)的内存里。
二、实验程序:
驱动源码:
C
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
static dev_t dev_num; //设备号
static int major = 0; //主设备号
static int minor = 0; //次设备号
struct cdev cdev_test; // cdev
struct class *class; //类
struct device *device; //设备
/*打开设备函数*/
static int cdev_test_open(struct inode *inode, struct file *file)
{
printk("This is cdev_test_open\r\n");
return 0;
}
/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
/*本章实验重点******/
char kbuf[32] = {0}; //定义写入缓存区kbuf
if (copy_from_user(kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据
{
printk("copy_from_user error\r\n");//打印copy_from_user函数执行失败
return -1;
}
printk("This is cdev_test_write\r\n");
printk("kbuf is %s\r\n", kbuf);
return 0;
}
/**从设备读取数据*/
static ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{
/*本章实验重点******/
char kbuf[32] = "This is cdev_test_read!";//定义内核空间数据
if (copy_to_user(buf, kbuf, strlen(kbuf)) != 0) // copy_to_user:内核空间向用户空间传数据
{
printk("copy_to_user error\r\n"); //打印copy_to_user函数执行失败
return -1;
}
printk("This is cdev_test_read\r\n");
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)
{
printk("This is cdev_test_release\r\n");
return 0;
}
/*设备操作函数,定义file_operations结构体类型的变量cdev_test_fops*/
struct file_operations cdev_test_fops = {
.owner = THIS_MODULE, //将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = cdev_test_open, //将open字段指向chrdev_open(...)函数
.read = cdev_test_read, //将open字段指向chrdev_read(...)函数
.write = cdev_test_write, //将open字段指向chrdev_write(...)函数
.release = cdev_test_release, //将open字段指向chrdev_release(...)函数
};
static int __init chr_fops_init(void) //驱动入口函数
{
/*注册字符设备驱动*/
int ret;
/*1 创建设备号*/
ret = alloc_chrdev_region(&dev_num, 0, 1, "alloc_name"); //动态分配设备号
if (ret < 0)
{
printk("alloc_chrdev_region is error\n");//打印动态分配设备号失败
}
printk("alloc_chrdev_region is ok\n");
major = MAJOR(dev_num); //获取主设备号
minor = MINOR(dev_num); //获取次设备号
printk("major is %d \r\n", major); //打印主设备号
printk("minor is %d \r\n", minor); //打印次设备号
/*2 初始化cdev*/
cdev_test.owner = THIS_MODULE;
cdev_init(&cdev_test, &cdev_test_fops);
/*3 添加一个cdev,完成字符设备注册到内核*/
cdev_add(&cdev_test, dev_num, 1);
/*4 创建类*/
class = class_create(THIS_MODULE, "test");
/*5 创建设备*/
device = device_create(class, NULL, dev_num, NULL, "test");
return 0;
}
static void __exit chr_fops_exit(void) //驱动出口函数
{
/*注销字符设备*/
unregister_chrdev_region(dev_num, 1); //注销设备号
cdev_del(&cdev_test); //删除cdev
device_destroy(class, dev_num); //删除设备
class_destroy(class); //删除类
}
module_init(chr_fops_init); //注册入口函数
module_exit(chr_fops_exit); //注册出口函数
MODULE_LICENSE("GPL v2");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
驱动对应的Makefile:
C
export ARCH=arm64
export CROSS_COMPILE=/home/book/rk/tspi/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
obj-m += char.o #此处要和你的驱动源文件同名
KDIR := /home/book/rk/tspi/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make#操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
APP:
/home/book/rk/tspi/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc test.c 可以交叉编译生成a.out
C
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]) //主函数
{
int fd; //定义int类型的文件描述符
char buf1[32] = {0}; //定义读取缓存区buf1
char buf2[32] = "nihao"; //定义写入缓存区buf2
fd = open("/dev/test", O_RDWR); //打开字符设备驱动
if (fd < 0)
{
perror("open error \n");
return fd;
}
read(fd, buf1, sizeof(buf1));//从/dev/test文件读取数据
printf("buf1 is %s \r\n", buf1); //打印读取的数据
write(fd,buf2,sizeof(buf2));//向/dev/test文件写入数据
close(fd);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24