- 参考正点原子视频,做记录以及简单使用。
首先创建如下所示文件夹及文件,文件的内容如下三个所示。。。
/************************chrdevbase.c*****************/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#define MAJOR_NUMBER 200
const char* CHR_NAME = "chr_test";
static int chrtest_open(struct inode *inode,struct file *filp)
{
printk("chrtest_open !!!!\n");
return 0;
}
//filp:要打开的设备文件(文件描述符)
//buf:返回给用户空间的数据缓冲区
//cnt:要读取的数据长度
//offt:相对于文件首地址的偏移
//return:返回读取到的字节数
static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
printk("chrtest_read !!!!\n");
return 0;
}
static ssize_t chrtest_write(struct file *filp,const char __user *buf, size_t cnt, loff_t *offt)
{
printk("chrtest_write !!!!\n");
return 0;
}
static int chrtest_release(struct inode *inode, struct file *filp)
{
printk("chrtest_release !!!!\n");
return 0;
}
static struct file_operations test_fops = { //实例化一个函数结构体
.owner = THIS_MODULE,
.open = chrtest_open,
.read = chrtest_read,
.write = chrtest_write,
.release = chrtest_release,
};
//int major;
int xxx_init(void) //模块入口函数
{
int retvalue = 0;
retvalue = register_chrdev(MAJOR_NUMBER, CHR_NAME, &test_fops); //设备注册
if(retvalue < 0){
printk("register failed!!!!\n"); //printf 运行在用户态,printk 运行在内核态。
}
else{
printk("register success!!!!\n");
}
return 0;
}
void xxx_exit(void) //模块退出函数
{
unregister_chrdev(200, CHR_NAME); //设备注销
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("Dual BSD/GPL");
/************************Makefile*************************/
KERNELDIR := /usr/src/linux-headers-$(shell uname -r) #内核源码目录
CURRENT_PATH := $(shell pwd) #shell脚本执行pwd,得知当前位置
obj-m := chrdevbase.o #生成文件名
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules #表明编译生成的是.ko模块文件
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean #删除生成文件
/*********************chrdevApp.c*************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
int ret = 0;
char* filename;
char readbuf[10];
char writebuf[50];
int fd = 0;
filename = argv[1];
//open(打开的文件名,读写权限);
fd = open(filename,O_RDWR); //返回文件描述符 O_RDWR表示读写模式
if(fd < 0){ //文件打开错误,返回<0
printf("canot open file %s \r\n", filename);
return -1;
}
else{
printf(" open file %s sucess \r\n", filename);
}
ret = read (fd, readbuf, 50); //从open()打开的fd文件读50个字节
if(ret < 0){
printf("read file %s error !!!\n", filename);
}
else{
printf("read file %s success !!!\n", filename);
}
ret = write(fd,writebuf, 50);//将writebuf中的50个字节写到fd对应的文件中
if(ret < 0){
printf("read file %s error !!!\n", filename);
}
else{
printf("read file %s success !!!\n", filename);
}
ret = close(fd);//关闭打开的fd文件
if(ret < 0){
printf("close file %s error !!!\n", filename);
}
else{
printf("close file %s success !!!\n", filename);
}
}
(https://mp.csdn.net/editor/html/113354613配合此链接的第一张图起理解以下的4点)
- 首先第一个文件chrdevbase.c, 此文件通过make编译生成chrdevbase.ko文件,然后通过sudo insmod chrdevbase.ko加载模块,然后用lsmod即可看到加载了的chrdevbase模块,这个时候代表模块已经加载到内核空间,驱动加载成功。。可用dmesg看到在chrdevbase.c中打印的register success!!!!。然后用cat /proc/device即可看到一个名为200 chr_test的chardev设备。
- 第二个文件chrdevAPP.c,此文件通过gcc chrdevApp.c -o chrdevApp生成可执行文件chrdevApp。然后在手动创建一个设备节点mknod /dev/chrdevbase c 200 0,(c:字符设备 200:主设备号 0:副设备号)此时通过ls /dev/chrdevbase -l即可查看设备节点chrdevbase文件。然后通过sudo ./chrdevApp /dev/chrdevbase代表执行chrdevApp,输入参数/dev/chrdevbase.
- 执行之后,可看到打印的chrdevApp中的内容,表明对文件的open file success等操作成功。而在用dmesg即可看到打印了chrtest_open等。。这表明 chrdevbaseAPP 使用 read 函数从 chrdevbase 设备读取数据,因此chrdevbase_read 函数就会执行并打印chrtest_read。。。
- 按照图来理解:用户空间的程序调用open函数对chrdevbase设备文件进行操作,系统调用到内核,内核根据手动创建的设备节点来找到chrdevbase设备驱动程序,然后驱动程序根据我们在chrdevbase.c写的read的函数来执行具体的操作。以上即为字符设备从应用程序到驱动程序的基本框架。。