字符设备驱动操作接口之read
a)回顾read系统调用函数
函数原型:
ssize_t read(int fd, void *buf, size_t count)
功能:从硬件设备中读取数据
参数:
fd:设备文件描述符,对应就是文件,本质最终对应硬件外设
buf:用于保存数据的用户缓冲区首地址
count:希望读取的字节数
返回值:返回实际读取的字节数
参考代码:
//读取字符串
char str[1024] = {0};
read(fd, str, 1024); //从硬件读取字符串数据保存到str数组中
printf("%s\n", str); //打印读取的字符串数据
//读取整形数
int status;
read(fd, &status, sizeof(status)); //从硬件读取数据保存到status变量中
printf("%d\n", status);
//读取数据
int arr[2] = {0};
read(fd, arr, 8); //从硬件设备读取8字节数据放到arr数组中
printf("%d %d\n", arr[0], arr[1]);
//读取结构体数据
struct A {
int a;
int b;
};
struct A aa;
read(fd, &aa, sizeof(aa)); //从硬件设备中读取一个结构体数据保存到aa中
printf("%d %d\n", aa.a, aa.b);
b)底层驱动的read接口
struct file_operations {
ssize_t (*read)(struct file *file, char __user *buf, size_t count, loff_t *ppos);
};
功能:负责从硬件外设中读取数据,然后将读取到的数据拷贝给应用程序,拷贝到用户缓冲区中(pstr,status,arr,aa)
起到了一个桥梁的作用,连接应用和硬件
硬件数据->read->应用用户缓冲区
参数:
file:文件指针,file和应用read的第一个参数fd是亲戚关系,通过fd能够找到file
buf:保存用户缓冲区的首地址(例如:buf=&status),同样底层驱动不能直接访问,太危险(*(int *)buf)
必须利用内核提供的内存拷贝函数:copy_to_user,将内核缓冲区中的数据拷贝到用户缓冲区中
int copy_to_user(void __user *to, void *from, int n)
功能:将内核缓冲区的数据拷贝到用户缓冲区中,帮你做地址的安全性检查
参数:
to:用户缓冲区的首地址
from:内核缓冲区的首地址
n:要拷贝的字节数
count:等于应用程序read的第三个参数(例如:count=sizeof(status)),保存希望读取的字节数
ppos:记录着上一次的读写位置,用于多次读取
编程步骤:
1.先获取到上一次的读写位置
loff_t pos = *ppos;
2.假如这次又读取了100字节,read接口返回之前记得更新读位置
*ppos = pos + 100;
返回值:返回实际读取的字节数,失败返回-1
调用关系:应用read->C库read->触发软中断->内核的sys_read->底层驱动read接口
案例:编写LED字符设备驱动,实现:获取灯的开关状态
实施步骤:
上位机执行:
cp 2.0/ 4.0 -fr
vim led_test.c
vim led_drv.c
增加从硬件读取数据接口
vim Makefile
make
arm-cortex_a9-linux-gnueabi-gcc -o led_test led_test.c
cp led_test led_drv.ko /opt/rootfs/home/drivers/
下位机测试
cd /home/drivers
insmod led_drv.ko
cat /proc/device 查看申请的主设备号
mknod /dev/myled c 244 0
./led_test on //观察打印的灯的状态
./led_test off
作业:编写LED驱动,利用read实现能够获取某个灯的开关状态
copyfrom再copyto