首页 > 系统相关 >Linux驱动开发

Linux驱动开发

时间:2023-06-26 09:34:02浏览次数:41  
标签:开发 file Linux 驱动 include retvalue 设备 chrdevbase

本文为一个简单的字符设备驱动,涉及驱动编写、测试程序编写、Makefile编写、驱动加载/卸载,运行于Linux虚拟机,不涉及底层配置。撰写本文的主要目的为记录一下驱动的开发流程,参考了正点原子的驱动开发指南。   驱动代码   创建文件夹 1_chrdevbase/ ,下属 APP/ 与 Driver/ 两个文件夹,前者放测试程序,后者放驱动代码。     在 Driver/ 下创建 chrdevbase.c,驱动代码如下   /*   * file name : chrdevbase.c  * description : 一个简单的字符设备demo  * author : 今朝无言  */   #include<linux/types.h> #include<linux/kernel.h> #include<linux/delay.h> #include<linux/ide.h> #include<linux/init.h> //引入module_init()以及module_exit() #include<linux/module.h> //与module相关的宏   MODULE_LICENSE("GPL"); MODULE_AUTHOR("今朝无言");   #define CHRDEVBASE_MAJOR 200 //主设备号,可通过 cat /proc/devices 查看所有设备及其主设备号 #define CHRDEVBASE_NAME "chrdevbase" //设备名   static char readbuf[100]; //读缓冲区 static char writebuf[100]; //写缓冲区 static char kerneldata[] = {"kernel data!"}; //内核数据,用于传递给测试APP,进行读取测试   /*   * description : 打开设备  * @param - inode : 传递给设备的inode  * @param - filp : 设备文件  * @return : 0 success;other failed  */ static int chrdevbase_open(struct inode *inode, struct file *filp){ printk("chrdevbase open!\n"); return 0; }   /*   * description : 从设备读取数据  * @param - filp : 设备文件  * @param - buf : 返回给用户空间的数据缓冲区  * @param - cnt : 要读取的数据长度  * @param - offt : 相对文件首地址的偏移  */ static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){ int retvalue = 0;   memcpy(readbuf, kerneldata, sizeof(kerneldata)); retvalue = copy_to_user(buf, readbuf, cnt);   if(retvalue == 0){ printk("kernel send data ok!\n"); } else { printk("kernel send data failed!\n"); }   return 0; }   /*   * description : 向设备写数据  * @param - filp : 设备文件  * @param - buf : 要写入设备的数据  * @param - cnt : 要写入的数据长度  * @param - offt : 相对文件首地址的偏移  */ static ssize_t chrdevbase_write(struct file *filp, const char *buf, size_t cnt, loff_t *offt){ int retvalue = copy_from_user(writebuf, buf, cnt);   retvalue = copy_from_user(writebuf, buf, cnt); if(retvalue == 0){ printk("kernel receive data: %s \n",writebuf); } else { printk("kernel receive data failed!\n"); }   return 0; }   /*   * description : 关闭设备  * @param - filp : 设备文件描述符  * @return : 0 success;other failed  */ static int chrdevbase_release(struct inode *inode, struct file *filp){ printk("chrdevbase release! \n"); return 0; }   /*  * chrdevbase的file_operations结构体  * file_operations的定义见Kernel/include/linux/fs.h  * 注意函数定义一定要相同,否则报`initialization from incompatible pointer type [-Werror=incompatible-pointer-types]`错  */ static struct file_operations chrdevbase_fops = { .owner= THIS_MODULE, .open= chrdevbase_open, .read= chrdevbase_read, .write= chrdevbase_write, .release= chrdevbase_release };   /*   * description : 驱动入口函数  */ static int __init chrdevbase_init(void){ //若函数没有参数,要加void,否则报`function declaration isn’t a prototype [-Werror=strict-prototypes]`错 int retvalue = 0;   retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops); if(retvalue < 0){ printk("chrdevbase driver register failed!\n"); } else { printk("chrdevbase driver register success!\n"); }   return 0; }   /*   * description : 驱动出口函数  */ static void __exit chrdevbase_exit(void){ unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME); printk("chrdevbase exit!\n");   return; }   //指定驱动入口和出口函数 module_init(chrdevbase_init); module_exit(chrdevbase_exit);   驱动代码Makefile   在 Driver/ 下创建 Makefile,内容如下   KERNELDIR := /lib/modules/4.15.0-189-generic/build #本机编译就/lib/modules/`uname -r`/build #交叉编译就使用对应的Kernel源码目录   CURRENT_PATH := $(shell pwd)   #要生成的模块名 obj-m := chrdevbase.o   build: kernel_modules   kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean     测试程序代码   在 APP/ 下创建 chrdevbaseAPP.c,代码如下   /*   * file name : chrdevbaseAPP.c  * description : chedevbase驱动的测试程序  * author : 今朝无言  */   #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdlib.h> #include<string.h>   static char usrdata[] = {"usr data!"}; //用户数据,用于传递给驱动,进行写入测试   // 用法:./chrdevbaseAPP /dev/chrdevbase arg int main(int argc, char *argv[]){ int fd, retvalue; char *filename; char readbuf[100], writebuf[100];   //检查参数 if(argc != 3){ printf("Error Usage!\n"); return -1; }   filename = argv[1];   //打开驱动文件 fd = open(filename, O_RDWR); if(fd < 0){ printf("Can't open file %s!\n", filename); return -2; }   //arg=1,从驱动文件读取数据 if(atoi(argv[2]) == 1){ retvalue = read(fd, readbuf, 50); if(retvalue < 0){ printf("read file %s failed!\n", filename); } else { printf("read data: %s\n", readbuf); } }   //arg=2,向驱动写数据 if(atoi(argv[2]) == 2){ memcpy(writebuf, usrdata, sizeof(usrdata)); retvalue = write(fd, writebuf, 50); if(retvalue < 0){ printf("write file %s failed!\n", filename); } else { printf("write file success!\n"); } }   //关闭设备 retvalue = close(fd); if(retvalue < 0){ printf("Can't close file %s!\n", filename); return -3; }   return 0; }   测试程序Makefile   在 APP/ 下创建 Makefile,内容如下   build: gcc chrdevbaseAPP.c -o chrdevbaseAPP   clean: rm chrdevbaseAPP   编译测试程序   编译驱动   驱动加载   使用 insmod 命令加载刚刚生成的驱动模块   sudo insmod chrdevbase.ko     执行   cat /proc/devices   查看驱动,如下图,可以看到驱动已经加载     创建设备节点文件   使用 mkmod 命令创建驱动节点   sudo mknod /dev/chrdevbase c 200 0   则创建字符设备文件/dev/chrdevbase,对该文件进行读写操作即可使用驱动,其中 ‘c’ 表示字符设备,200为主设备号,0为次设备号。   测试   进入 APP/ 文件夹,执行   sudo ./chrdevbaseAPP /dev/chrdevbase 1   进行设备读取测试,结果如下     可以看到用户接收到了从内核传递来的数据 ‘kernel data’ 。     执行   sudo ./chrdevbaseAPP /dev/chrdevbase 2   进行设备写入测试,结果如下       查看最后6条日志消息:   dmesg | tail -6     其中前三条是前面进行读取测试的日志输出,后三条是进行写入测试的日志输出,可以看到内核接收到了用户发送来的数据 ‘usr data’ 。   驱动卸载   使用 rmmod 命令卸载驱动:   sudo rmmod chrdevbase.ko 再使用 cat /proc/devices 查看,将发现 chrdevbase 设备已被卸载。     加载/卸载模块时的日志如下:        

标签:开发,file,Linux,驱动,include,retvalue,设备,chrdevbase
From: https://www.cnblogs.com/kn-zheng/p/17504510.html

相关文章

  • [ARM 汇编]高级部分—ARM汇编编程实战—3.3.2 嵌入式开发环境搭建
    搭建一个嵌入式开发环境主要包括以下几个部分:安装交叉编译器配置集成开发环境(IDE)安装调试工具下载和烧录程序接下来,我们将详细介绍每个部分,并提供相应的实例。安装交叉编译器交叉编译器是用于将您编写的ARM汇编代码编译成可执行文件的工具。在本教程中,我们将使用GNUAR......
  • Linux多线程09-互斥锁
    为避免线程更新共享变量时出现问题,可以使用互斥量(mutex是mutualexclusion的缩写)来确保同时仅有一个线程可以访问某项共享资源。可以使用互斥量来保证对任意共享资源的原子访问。互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。试......
  • Linux多线程10-死锁
    有时,一个线程需要同时访问两个或更多不同的共享资源,而每个资源又都由不同的互斥量管理。当超过一个线程加锁同一组互斥量时,就有可能发生死锁。两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状......
  • Linux多线程12-生产者和消费者模型
    一个最简单的生产者消费者模型/*生产者消费者模型(粗略版)*/#include<stdio.h>#include<pthread.h>#include<stdlib.h>#include<unistd.h>structNode{intnum;structNode*next;};//头节点structNode*head=NULL;void*producer(void*arg){......
  • Linux多线程11-读写锁
    当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。但是考虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想读取这个共享资源,但是由于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读访问共享资源了,但是实际上多个线程同时......
  • Linux多线程14-信号量
    信号量的类型sem_tintsem_init(sem_t*sem,intpshared,unsignedintvalue);初始化信号量参数:-sem:信号量变量地址-pshared:0用在线程间,非0用在进程间-value:信号量中的值intsem_destroy(sem_t*sem);释放资......
  • 驱动开发:内核物理内存寻址读写
    在某些时候我们需要读写的进程可能存在虚拟内存保护机制,在该机制下用户的CR3以及MDL读写将直接失效,从而导致无法读取到正确的数据,本章我们将继续研究如何实现物理级别的寻址读写。首先,驱动中的物理页读写是指在驱动中直接读写物理内存页(而不是虚拟内存页)。这种方式的优点是它能够......
  • Linux多线程13-条件变量
    上节代码存在的问题:生产者已经没有数据了,消费者还在while循环判断是否有数据,浪费资源没有数据了应该通知生产者生产,生产好了通知消费者消费这就需要条件变量pthread_cond_tintpthread_cond_init(pthread_cond_t*restrictcond,constpthread_con......
  • Linux-rsyslog日志格式修改
    0.背景rsyslog是linux系统中用来实现日志功能的服务。默认已经安装,并且自动启用。作用:主要用来采集日志,不生产日志支持输出日志到各种数据库,如MySQL,PostgreSQL,MongoDBElasticSearch,等等;通过RELP+TCP实现数据的可靠传输(基于此结合丰富的过滤条件可以建立一种可靠的......
  • Linux多线程01-线程概述
    线程概述与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段。(传统意义上的UNIX进程只是多线程程序......