0%

应用层的 write 如何调用驱动里的 write

应用层的 write 如何调用驱动里的 write

1 编写应用程序测试 hello_dev 驱动

1.1 编写测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>

#define DATA_LEN (64)

int main(int argc, char *argv[]) {
char buf[DATA_LEN] = "Hello, world!";
int fd = open("/dev/hello", O_RDWR);
if (-1 == fd) {
perror("open file error\r\n");
return -1;
}
printf("open success\r\n");

int w_len = write(fd, buf, DATA_LEN);
memset(buf, 0, DATA_LEN);
int r_len = read(fd, buf, DATA_LEN);
printf("%d %d\r\n", w_len, r_len);
printf("%s\r\n", buf);

return 0;
}
  • 打开 “/dev/hello” 设备,写入数据,然后再读取,控制台输出write、read函数的返回值、缓冲区中的内容

1.2 创建字符设备

  • 执行 insmod 命令加载驱动到内核,
1
$ sudo insmod hello_dev.ko
  • 使用 mknod 命令创建设备,
1
2
3
4
5
6
7
MKNOD(1)                                                              User Commands                                                             MKNOD(1)
NAME
mknod - make block or character special files
SYNOPSIS
mknod [OPTION]... NAME TYPE [MAJOR MINOR]
DESCRIPTION
Create the special file NAME of the given TYPE.
参数 说明
NAME 设备名称
TYPE 设备类型,b表示块设备,c表示字符设备,p表示FIFO
MAJOR 主设备号
MINOR 次设备号
  • hello_dev 驱动文件中定义主设备号为 232,次设备号为 0,于是创建字符设备,
1
sudo mknod /dev/hello c 232 0

此时执行 ls -l 命令查看设备是否存在,

1
2
$ ls -l /dev/hello
crw-r--r-- 1 root root 232, 0 Nov 6 23:52 /dev/hello

1.2 编译、运行测试程序

  • 使用 gcc 编译测试程序,
1
$ gcc -o hello_dev_test hello_dev_test.c
  • 运行测试程序,查看结果,
1
2
3
$ ./hello_dev_test 
open success
0 0

这里 w_len、r_len 为 0,缓冲区为空,因为 hello_dev 驱动源码中,write、read 函数输出内核日志后就直接返回 0 了

  • 查看linux内核日志,
1
2
3
4
$ dmesg | tail -3
[ 9040.652155] hello_open
[ 9040.652186] hello_write
[ 9040.652188] hello_read

hello_dev 驱动程序中定义的 open、write、read 函数确实被调用了

2 从应用层 write 调用到驱动程序中定义的write

不支持在 Docs 外粘贴 block

  • 应用进程调用 c 函数库中定义的 write
  • c write 函数接着陷入系统调用,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;

if (f.file) {
loff_t pos = file_pos_read(f.file);
ret = vfs_write(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
fdput_pos(f);
}

return ret;
}

linux 的系统调用由 SYSCALL_DEFINE 定义

  • write 系统调用根据文件描述符获取到实际的 file 结构体对象,作为其中1个参数,调用 vfs_write 函数,
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
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;

if (!(file->f_mode & FMODE_WRITE))
return -EBADF;
if (!(file->f_mode & FMODE_CAN_WRITE))
return -EINVAL;
if (unlikely(!access_ok(VERIFY_READ, buf, count)))
return -EFAULT;

ret = rw_verify_area(WRITE, file, pos, count);
if (!ret) {
if (count > MAX_RW_COUNT)
count = MAX_RW_COUNT;
file_start_write(file);
ret = __vfs_write(file, buf, count, pos);
if (ret > 0) {
fsnotify_modify(file);
add_wchar(current, ret);
}
inc_syscw(current);
file_end_write(file);
}

return ret;
}
  • vfs_write 函数调用底层的 __vfs_write 函数,
1
2
3
4
5
6
7
8
9
ssize_t __vfs_write(struct file *file, const char __user *p, size_t count, loff_t *pos)
{
if (file->f_op->write)
return file->f_op->write(file, p, count, pos);
else if (file->f_op->write_iter)
return new_sync_write(file, p, count, pos);
else
return -EINVAL;
}

在 __vfs_write 函数中,判断此文件类型是否有定义 write 函数,如果有则直接调用返回结果。file 结构体的 f_op 字段是 file_operations 类型,在 hello_dev 驱动源码中,

1
2
3
4
5
6
7
8
9
10
devNum = MKDEV(reg_major, reg_minor);
............
gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
gFile->open = hello_open;
gFile->read = hello_read;
gFile->write = hello_write;
gFile->owner = THIS_MODULE;
cdev_init(gDev, gFile);
cdev_add(gDev, devNum, 1);

将字符设备和 file_operations 结构体对象绑定,然后将字符设备和设备号绑定