07、llseek 定位设备驱动实验 *
假如现在有这样一个场景, 将两个字符串依次进行写入, 并对写入完成的字符串进行读取, 如果仍采用之前的方式, 第二次的写入值会覆盖第一次写入值, 那要如何来实现上述功能呢? 这就要轮到 llseek 出场了 。
一、API说明
在应用程序中使用 lseek 函数进行读写位置的调整, 该函数的具体使用说明如下所示: lseek 函数
::: 函数原型:
off_t lseek(int fd, off t offset, int whence);
头文件:
#include <sys/types.h>
#include <unistd.h>
函数作用:
移动文件的读写位置。
参数含义:
fd:文件描述符;
off toffset: 偏移量,单位是字节的数量,可以正负,如果是负值表示向前移动:如果是正值,表示向后移动。
whence:当前位置的基点,可以使用以下三组值。
SEEK SET:相对于文件开头
SEEK CUR:相对于当前的文件读写指针位置
SEEK END:相对于文件末尾
函数返回值:
成功返回当前位移大小,失败返回-1 :::
函数使用示例:
把文件位置指针设置为 5:
lseek(fd,5,SEEK_SET);
把文件位置设置成文件末尾:
lseek(fd,0,SEEK_END);
确定当前的文件位置:
lseek(fd,0,SEEK_CUR);
二、实验代码
驱动代码:
C
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
#define BUFSIZE 1024//设置最大偏移量为1024
static char mem[BUFSIZE] = {0};//设置数据存储数组mem
struct device_test{
dev_t dev_num; //设备号
int major ; //主设备号
int minor ; //次设备号
struct cdev cdev_test; // cdev
struct class *class; //类
struct device *device; //设备
char kbuf[32];
};
static struct device_test dev1;
static int cdev_test_open(struct inode *inode, struct file *file)
{
file->private_data=&dev1;//设置私有数据
return 0;
}
/*从设备读取数据*/
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
loff_t p = *off;//将读取数据的偏移量赋值给loff_t类型变量p
int i;
size_t count = size;
if(p > BUFSIZE){
return 0;
}
if(count > BUFSIZE - p){
count = BUFSIZE - p;
}
if(copy_to_user(buf,mem+p,count)){//将mem中的值写入buf,并传递到用户空间
printk("copy_to_user error \n");
return -1;
}
for(i=0;i<20;i++){
printk("buf[%d] is %c\n",i,mem[i]);//将mem中的值打印出来
}
printk("mem is %s,p is %llu,count is %ld\n",mem+p,p,count);
*off = *off + count;//更新偏移值
return count;
}
/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
loff_t p = *off;//将读取数据的偏移量赋值给loff_t类型变量p
size_t count = size;
if(p > BUFSIZE){
return 0;
}
if(count > BUFSIZE - p){
count = BUFSIZE - p;
}
if(copy_from_user(mem+p,buf,count)){//将buf中的值,从用户空间传递到内核空间
printk("copy_to_user error \n");
return -1;
}
printk("mem is %s,p is %llu\n",mem+p,p);//打印写入的值
*off = *off + count;//更新偏移值
return count;
}
static int cdev_test_release(struct inode *inode, struct file *file)
{
return 0;
}
static loff_t cdev_test_llseek(struct file *file, loff_t offset, int whence)
{
loff_t new_offset;//定义loff_t类型的新的偏移值
switch(whence)//对lseek函数传递的whence参数进行判断
{
case SEEK_SET:
if(offset < 0){
return -EINVAL;
break;
}
if(offset > BUFSIZE){
return -EINVAL;
break;
}
new_offset = offset;//如果whence参数为SEEK_SET,则新偏移值为offset
break;
case SEEK_CUR:
if(file->f_pos + offset > BUFSIZE){
return -EINVAL;
break;
}
if(file->f_pos + offset < 0){
return -EINVAL;
break;
}
new_offset = file->f_pos + offset;//如果whence参数为SEEK_CUR,则新偏移值为file->f_pos + offset,file->f_pos为当前的偏移值
break;
case SEEK_END:
if(file->f_pos + offset < 0){
return -EINVAL;
break;
}
new_offset = BUFSIZE + offset;//如果whence参数为SEEK_END,则新偏移值为BUFSIZE + offset,BUFSIZE为最大偏移量
break;
default:
break;
}
file->f_pos = new_offset;//更新file->f_pos偏移值
return new_offset;
}
/*设备操作函数*/
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(...)函数
.llseek = cdev_test_llseek,
};
static int __init timer_dev_init(void) //驱动入口函数
{
/*注册字符设备驱动*/
int ret;
/*1 创建设备号*/
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); //动态分配设备号
if (ret < 0)
{
goto err_chrdev;
}
printk("alloc_chrdev_region is ok\n");
dev1.major = MAJOR(dev1.dev_num); //获取主设备号
dev1.minor = MINOR(dev1.dev_num); //获取次设备号
printk("major is %d \r\n", dev1.major); //打印主设备号
printk("minor is %d \r\n", dev1.minor); //打印次设备号
/*2 初始化cdev*/
dev1.cdev_test.owner = THIS_MODULE;
cdev_init(&dev1.cdev_test, &cdev_test_fops);
/*3 添加一个cdev,完成字符设备注册到内核*/
ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
if(ret<0)
{
goto err_chr_add;
}
/*4 创建类*/
dev1. class = class_create(THIS_MODULE, "test");
if(IS_ERR(dev1.class))
{
ret=PTR_ERR(dev1.class);
goto err_class_create;
}
/*5 创建设备*/
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
if(IS_ERR(dev1.device))
{
ret=PTR_ERR(dev1.device);
goto err_device_create;
}
return 0;
err_device_create:
class_destroy(dev1.class); //删除类
err_class_create:
cdev_del(&dev1.cdev_test); //删除cdev
err_chr_add:
unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
err_chrdev:
return ret;
}
static void __exit timer_dev_exit(void) //驱动出口函数
{
/*注销字符设备*/
unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
cdev_del(&dev1.cdev_test); //删除cdev
device_destroy(dev1.class, dev1.dev_num); //删除设备
class_destroy(dev1.class); //删除类
}
module_init(timer_dev_init);
module_exit(timer_dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("linux");
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
应用代码:
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类型文件描述符
unsigned int off;//定义读写偏移位置
char readbuf[13] = {0};//定义读取缓冲区readbuf
char readbuf1[19] = {0};//定义读取缓冲区readbuf1
fd = open("/dev/test",O_RDWR,666);//打开/dev/test设备
if(fd < 0 ){
printf("file open error \n");
}
write(fd,"hello world",13);//向fd写入数据hello world
off = lseek(fd,0,SEEK_CUR);//读取当前位置的偏移量
printf("off is %d\n",off);
off = lseek(fd,0,SEEK_SET);//将偏移量设置为0
printf("off is %d\n",off);
read(fd,readbuf,sizeof(readbuf));//将写入的数据读取到readbuf缓冲区
printf("read is %s\n",readbuf);
off = lseek(fd,0,SEEK_CUR);//读取当前位置的偏移量
printf("off is %d\n",off);
off = lseek(fd,-1,SEEK_CUR);//将当前位置的偏移量向前挪动一位
printf("off is %d\n",off);
write(fd,"Linux",6);//向fd写入数据Linux
off = lseek(fd,0,SEEK_CUR);//读取当前位置的偏移量
printf("off is %d\n",off);
off = lseek(fd,0,SEEK_SET);//将偏移量设置为0
printf("off is %d\n",off);
read(fd,readbuf1,sizeof(readbuf1));//将写入的数据读取到readbuf1缓冲区
printf("read is %s\n",readbuf1);
off = lseek(fd,0,SEEK_CUR);//读取当前位置的偏移量
printf("off is %d\n",off);
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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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