一、读操作实现
ssize_t xxx_read(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos);
完成功能:读取设备产生的数据
参数:
filp:指向open产生的struct file类型的对象,表示本次read对应的那次open
pbuf:指向用户空间一块内存,用来保存读到的数据
count:用户期望读取的字节数
ppos:对于需要位置指示器控制的设备操作有用,用来指示读取的起始位置,读完后也需要变更位置指示器的指示位置
返回值:
本次成功读取的字节数,失败返回-1
put_user(x,ptr)
x:char、int类型的简单变量名
unsigned long copy_to_user (void __user * to, const void * from, unsigned long n)
这个函数通常用于从内核空间复制数据到用户空间。
to 参数是指向用户空间的目标地址,您希望将数据复制到这个地址。
from 参数是指向内核空间的源地址,您希望从这个地址复制数据。
n 参数是要复制的字节数。
成功为返回0,失败非0
二、写操作实现
ssize_t xxx_write (struct file *filp, const char __user *pbuf, size_t count, loff_t *ppos);
完成功能:向设备写入数据
参数:
filp:指向open产生的struct file类型的对象,表示本次write对应的那次open
pbuf:指向用户空间一块内存,用来保存被写的数据
count:用户期望写入的字节数
ppos:对于需要位置指示器控制的设备操作有用,用来指示写入的起始位置,写完后也需要变更位置指示器的指示位置
返回值:
本次成功写入的字节数,失败返回-1
get_user(x,ptr)
x:char、int类型的简单变量名
unsigned long copy_from_user (void * to, const void __user * from, unsigned long n)
这个函数通常用于从用户空间复制数据到内核空间。
to:参数是指向内核空间的目标地址,您希望将数据复制到这个地址。
from:参数是指向用户空间的源地址,您希望从这个地址复制数据。
n:参数是要复制的字节数。
成功为返回0,失败非0
实现代码:
mychar.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#define BUF_LEN 100
int major = 11; //主设备号
int minor = 0; //次设备号
int char_num = 1; //设备号数量
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen = 0;
int mychar_open (struct inode *pnode, struct file *pfile)//打开设备
{
printk("open\n");
return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)//关闭设备
{
printk("close\n");
return 0;
}
ssize_t mychar_read (struct file *pfile, char __user *puser, size_t count, loff_t *p_pos) {
int size = 0;
int ret = 0;
// 确定要读取的数据长度,如果请求大于设备当前数据长度,则读取全部可用数据
if (count > curlen) {
size = curlen;
}
else {
size = count;
}
// 将设备数据复制到用户空间缓冲区
ret = copy_to_user(puser, mydev_buf, size);
if(ret) {
printk("copy_to_user failed\n");
return -1;
}
// 移动设备内部缓冲区,去除已读取的数据
memcpy(mydev_buf, mydev_buf + size, curlen - size);
curlen = curlen - size;
// 返回实际读取的字节数
return size;
}
ssize_t mychar_write (struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos) {
int size = 0;
int ret = 0;
// 确定要写入的数据长度,如果请求大于设备缓冲区剩余空间,则写入剩余空间大小
if (count > BUF_LEN - curlen) {
size = BUF_LEN - curlen;
}
else {
size = count;
}
// 从用户空间复制数据到设备缓冲区
ret = copy_from_user(mydev_buf + curlen, puser, size);
if(ret) {
printk("copy_from_user failed\n");
return -1;
}
// 更新设备缓冲区中的数据长度
curlen = curlen + size;
// 返回实际写入的字节数
return size;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.read = mychar_read,
.write = mychar_write,
.release = mychar_close,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major, minor);
/* 手动申请设备号 */
ret = register_chrdev_region(devno, char_num, "mychar");
if (ret) {
/* 动态申请设备号 */
ret = alloc_chrdev_region(&devno, minor, char_num, "mychar");
if(ret){
printk("get devno failed\n");
return -1;
}
/*申请成功 更新设备号*/
major = MAJOR(devno);
}
cdev_init(&mydev, &myops);
mydev.owner = THIS_MODULE;
cdev_add(&mydev, devno, char_num);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major, minor);
printk("exit %d\n", devno);
/* 从内核中移除一个字符设备 */
cdev_del(&mydev);
/* 回收设备号 */
unregister_chrdev_region(devno, char_num);
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
testmychar_app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd = -1;
char buf[32] = "";
if(argc < 2) {
printf("The argument is too few\n");
return -1;
}
fd = open(argv[1], O_RDWR);
if(fd < 0) {
perror("open");
return -1;
}
write(fd, "hello", 6);
read(fd, buf, 32);
printf("buf = %s\n", buf);
close(fd);
fd = -1;
return 0;
}
Makefile
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/myubuntu/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
# obj-m += myhello.o
# obj-m += mytest.o
# mytest-objs = test.o func.o
# obj-m += testparam.o
obj-m += mychar.o
endif
实现结果:
避免使用全局变量
这些代码都是全局变量
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen = 0;
改动之后的mychar.c代码
把这些全局变量封装在结构体中,这样可以更好地管理数据,降低代码的耦合性,提高代码的可维护性和可扩展性
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#define BUF_LEN 100
int major = 11; //主设备号
int minor = 0; //次设备号
int char_num = 1; //设备号数量
struct mychar_dev
{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen;
};
struct mychar_dev gmydev;
int mychar_open (struct inode *pnode, struct file *pfile)//打开设备
{
pfile->private_data = (void *) (container_of(pnode->i_cdev, struct mychar_dev, mydev));
printk("open\n");
return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)//关闭设备
{
printk("close\n");
return 0;
}
ssize_t mychar_read (struct file *pfile, char __user *puser, size_t count, loff_t *p_pos) {
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
int size = 0;
int ret = 0;
// 确定要读取的数据长度,如果请求大于设备当前数据长度,则读取全部可用数据
if (count > pmydev->curlen) {
size = pmydev->curlen;
}
else {
size = count;
}
// 将设备数据复制到用户空间缓冲区
ret = copy_to_user(puser, pmydev->mydev_buf, size);
if(ret) {
printk("copy_to_user failed\n");
return -1;
}
// 移动设备内部缓冲区,去除已读取的数据
memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);
pmydev->curlen -= size;
// 返回实际读取的字节数
return size;
}
ssize_t mychar_write (struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos) {
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
int size = 0;
int ret = 0;
// 确定要写入的数据长度,如果请求大于设备缓冲区剩余空间,则写入剩余空间大小
if (count > BUF_LEN - pmydev->curlen) {
size = BUF_LEN - pmydev->curlen;
}
else {
size = count;
}
// 从用户空间复制数据到设备缓冲区
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
if(ret) {
printk("copy_from_user failed\n");
return -1;
}
// 更新设备缓冲区中的数据长度
pmydev->curlen += size;
// 返回实际写入的字节数
return size;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.read = mychar_read,
.write = mychar_write,
.release = mychar_close,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major, minor);
/* 手动申请设备号 */
ret = register_chrdev_region(devno, char_num, "mychar");
if (ret) {
/* 动态申请设备号 */
ret = alloc_chrdev_region(&devno, minor, char_num, "mychar");
if(ret){
printk("get devno failed\n");
return -1;
}
/*申请成功 更新设备号*/
major = MAJOR(devno);
}
cdev_init(&gmydev.mydev, &myops);
gmydev.mydev.owner = THIS_MODULE;
cdev_add(&gmydev.mydev, devno, char_num);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major, minor);
printk("exit %d\n", devno);
/* 从内核中移除一个字符设备 */
cdev_del(&gmydev.mydev);
/* 回收设备号 */
unregister_chrdev_region(devno, char_num);
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);