1、字符设备定义
应用程序按字节/字符来读写数据的设备,不支持随机存取数据,系统直接从设备读取/写入每一个字符。
2、字符设备抽象
Linux内核中将字符设备抽象成一个具体的数据结构(struct cdev),理解为字符设备对象。 字符设备的打开、读写、关闭等操作接口(file_operations)。 创建一个文件(设备节点)绑定对象的cdev
3、字符设备相关概念
3.1、设备号
主设备号区分设备类别,次设备号标识具体的设备。cdev结构体被内核用来记录设备号,而在使用设备时,我们通常会打开设备节点,通过设备节点的inode结构体、 file结构体最终找到file_operations结构体,并从file_operations结构体中得到操作设备的具体方法。 dev_t用来表示设备编号,dev_t是一个32位的数,其中,高12位表示主设备号,低20位表示次设备号。
3.2、设备节点
Linux中设备节点是通过“mknod”命令来创建的。一个设备节点其实就是一个文件, Linux中称为设备文件。设备节点被创建在/dev下,是连接内核与用户层的枢纽。
sudo mknod /dev/chardev c 236 0
4、数据结构
这三个数据结构体都是存在(内核源码/include/linux/fs.h)
4.1、文件操作结构体file_operations
系统调用和驱动程序关联起来的关键数据结构
struct file_operations {
struct module *owner;
ssize_t (*read)(struct file *, char __user *, size_t loff_t *);
ssize_t (*write)(struct file const char __user *, size_t, loff_t *);
int (*open)(struct inode *, struct file *);
int (* release)(struct inode *, struct file *);
......
};
4.2 文件描述结构体
内核中用file结构体来表示每个打开的文件,每打开一个文件,内核会创建一个结构体
struct file {
{......};
const struct file_operations *f_op;
void *private_data;
{......};
};
4.3、节点inode结构体
内核使用inode结构体在内核内部表示一个文件,它是Linux 管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁.
struct inode{
dev_t i_rdev;
{......};
union{
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
char *i_link;
unsigned i_dir_seq;
};
{......};
};
5、设备号的注册申请及注销归还
5.1、宏定义常量进行申请
int register_chrdev_region(dev_t from, unsigned count, const char *name)
5.2、内核自动分配,成功会返回一个指针
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
5.3、注销删除设备
void unregister_chrdev_region(dev_t from, unsigned count)
6、字符设备驱动模块(chrdevbase.c)
//头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define MY_NAME "chardev"
int major = 0;
char kbuf[128] = {0};
int my_open(struct inode *inode, struct file *file)
{
printk("chardev open!\n");
return 0;
}
ssize_t my_read(struct file *file, char __user *ubuf, size_t size, loff_t *offset)
{
if(size > sizeof(kbuf)){
size = sizeof(kbuf);
}
if(copy_to_user(ubuf, kbuf, size)){
printk("copy data to user failed.\n");
return -EIO;
}
printk("user read data ok!\n");
return size;
}
ssize_t my_write(struct file *file, const char __user *ubuf, size_t size, loff_t *offset)
{
if(size > sizeof(kbuf)){
size = sizeof(kbuf);
}
if(copy_from_user(kbuf, ubuf, size)){
printk("copy data to kernel failed.\n");
return -EIO;
}
printk("user write data ok!\n");
return size;
}
int my_close(struct inode *inode, struct file *file)
{
printk("chardev close!\n");
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = my_open,
.read = my_read,
.write = my_write,
.release = my_close
};
static int __init mycdev_init(void)
{
major = register_chrdev(0, MY_NAME, &fops);
if(major < 0)
{
printk("chardev reg failed!\n");
return -1;
}
printk("chardev reg successed\n");
return 0;
}
static void __exit mycdev_exit(void)
{
printk("hello linux %s\n"," chardev exit");
unregister_chrdev(major, MY_NAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zbl");
7、用户应用程序(chrdevbaseapp.c)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
char buf[32] = "zbl tspi.";
char ubuf[64] = {0};
int fd = open("/dev/chardev",O_RDWR);
if(fd == -1)
{
printf("open failed.\n");
return -1;
}
memcpy(ubuf,buf,sizeof(buf));
printf("write ubuf = %s\n", ubuf);
write(fd,ubuf,sizeof(ubuf));
memset(ubuf, 0, sizeof(ubuf));
read(fd,ubuf,1024);
printf("read ubuf = %s\n",ubuf);
close(fd);
return 0;
}
**8、交叉编译makefile**
PWD ?= $(shell pwd)
KERNELDIR := /home/zbl/tspi-rk3566/sdk/linux/kernel
CROSS_COMPILE ?= /home/zbl/tspi-rk3566/sdk/linux/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
CC := $(CROSS_COMPILE)gcc
obj-m += chrdevbase.o
module:
make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 modules
@# -C 表示从当前目录切换到内核源码目录下,借助内核源码makefile进行make编译
@# M=$(PWD) 表示只编译当前目录下的驱动
@# ARCH=arm64 指定编译架构
$(CC) chrdevbaseapp.c -o app
@# 交叉编译应用程序
.PHONE:clean
clean:
make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 clean
rm app
**9、修改终端printk显示等级**
////终端消息在串口才可以查看,ssh无法打印终端消息,并且需要修改printk的等级为8////
root@localhost:/home/lckfb/zbl-kernel-modules# ./app
root@localhost:/home/lckfb/zbl-kernel-modules# cat /proc/sys/kernel/printk
4 4 1 7
root@localhost:/home/lckfb/zbl-kernel-modules# echo 8 > /proc/sys/kernel/printk
root@localhost:/home/lckfb/zbl-kernel-modules# cat /proc/sys/kernel/printk
8 4 1 7
10、执行流程
1、编写内核驱动chrdevbase.c
2、编写应用程序chrdevbaseapp.c
3、编写makefile,添加交叉编译工具及编译应用执行文件
4、.ko及app应用执行文件导入开发板 cmd 使用adb命令或者局域网内登录ssh 传输 : adb push xxx xxx
5、修改执行权限 chmod 777 app chrdevbase.ko
6、加载内核启动 sudo insmod chrdevbase.ko
lckfb@linux:~/zbl-kernel-modules$ sudo insmod chrdevbase.ko
[ 4343.651750] reg successed
7、查看驱动lsmod
lckfb@linux:~/zbl-kernel-modules$ lsmod
Module Size Used by
chrdevbase 16384 0
bcmdhd 1048576 0
8、查看驱动主设备号
cat /proc/devices
226 drm
236 chardev
237 hidraw
238 rpmb
239 ttyGS
9、创建设备节点文件 sudo mknod /dev/chardev c(字符设备) 236(主设备号) 0(从设备号)
lckfb@linux:~/zbl-kernel-modules$ sudo mknod /dev/chardev c 236 0
10、查看节点是否加载成功 ls -l /dev/chardev
lckfb@linux:~/zbl-kernel-modules$ ll /dev/chardev
crw-r--r-- 1 root root 236, 0 Jun 4 13:59 /dev/chardev
11、修改节点权限 sudo chmod 777 /dev/chardev
lckfb@linux:~/zbl-kernel-modules$ ll /dev/chardev
crwxrwxrwx 1 root root 236, 0 Jun 4 13:59 /dev/chardev
12、执行应用程序 ./app
root@localhost:/home/lckfb/zbl-kernel-modules# ./app
[ 4208.722682] open!
[ 4208.722768] read!
[ 4208.722785] write!
[ 4208.722801] close!
root@localhost:/home/lckfb/zbl-kernel-modules/04# ./app
[ 1090.308736] chardev open!
write ubuf = zbl tspi.
read ubuf = zbl tspi.
[ 1090.309171] user write data ok!
[ 1090.309209] user read data ok!
[ 1090.309500] chardev close!
13、卸载内核驱动 sudo rmmod chrdevbase.ko
root@localhost:/home/lckfb/zbl-kernel-modules# sudo rmmod chrdevbase.ko
[ 4236.212310] hello world mydev exit
14、删除设备节点文件 sudo rm /dev/chardev
root@localhost:/home/lckfb/zbl-kernel-modules# sudo rm /dev/chardev
标签:11,字符,file,struct,--,zbl,dev,chardev,设备 From: https://www.cnblogs.com/zblblog/p/18232342