Linux设备驱动工程师之路——简单字符设备驱动程序

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

Linux设备驱动工程师之路——简单字符设备驱动程序

K-Style

转载请注明来自于衡阳师范学院08电2  K-Style  http://blog.csdn.net/ayangke,QQ:843308498 邮箱:yangkeemail@qq.com

 

一、重要知识点

 

1. 主次设备号

dev_t

         dev_t是内核中用来表示设备编号的数据类型;

int MAJOR(dev_t dev)

int MINOR(dev_t dev)

         这两个宏抽取主次设备号。

dev­_t MKDEV(unsigned int major, unsignedint minor)

         这个宏由主/次设备号构造一个dev_t结构。

 

2. 分配和释放设备号

int register_chardev_region(dev_t first,unsigned int count, char *name)

         静态申请设备号。

Int alloc_chardev_region(dev_t *dev,unsigned int firstminor, unsigned int count, char *name)

         动态申请设备号,注意第一个参数是传地址,而静态则是传值。

        

         3. 几种重要的数据结构

struct file

         file结构代表一个打开的文件,它由内核在open时创建,并传递给该文件上进行操作的所有函数,直到最后的close函数。

         file结构private_data是跨系统调用时保存状态信息非常有用的资源。

         file结构的f_ops 保存了文件的当前读写位置。

struct inode

         内核用inode代表一个磁盘上的文件,它和file结构不同,后者表示打开的文件描述符。对于单个文件,可能会有许多个表示打开文件的文件描述符file结构,但他们都指单个inode结构。inode的dev_t i_rdev成员包含了真正的设备编号,struct cdev *i_cdev包含了指向struct cdev结构的指针。

struct file_operations

         file_operations结构保存了字符设备驱动程序的方法。

 

         4. 字符设备的注册和注销

struct cdev *cdev_alloc(void);

void cdev_init(struct cdev *dev, structfile_operations *fops);

int cdev_add(struct cdev *dev, dev_t num,unsigned int count);

void cdev_del(struct cdev *dev);

         用来管理cdev结构的函数,内核中使用该结构表示字符设备。注意cdev_add函数的count参数为次设备的个数,要想拥有多个次设备,就必须将该参数设为次设备的个数。

 

         5. 并发处理

         信号量和自旋锁的区别,使用信号量时当调用进程试图获得一个锁定了的锁时会导致进程睡眠,而自旋锁则是一直循法的等待一直到该锁解锁了为止。

         1)信号量

DECLARE_MUTEX(name);

DECLARE_MUTEX_LOCKED(name);

         声明和初始化用在互斥模式中的信号量的两个宏

void init_MUTEX(struct semaphore *sem)

void init_MUTEX_LOCKER(struct semaphore*sem);

         这两个函数可以在运行时初始化信号量

void down(struct semaphore *sem);

int down_interruptible(struct semaphore*sem);

int down_trylock(struct semahpore *sem);

void up(struct semaphore *sem);

         锁定和解锁信号量。如果必要,down会将调用进程置于不可中断的休眠状态;相反,down_interruptible可被信号中断。down_trylock不会休眠,并且会在信号量不可用时立即返回。锁定信号量的代码最后必须使用up解锁该信号量。

         2)自旋锁

spionlock_t lock = SPIN_LOCK_UNLOCKED;

spin_lock_init(spinlock_t *lock);

初始化自旋锁的两种方式。

         voidspin_lock(spinlock_t *lock);

锁定自旋锁

         voidspin_unlock(spinlock_t *lock);

         解锁自旋锁

        

二、驱动代码

 

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
 
 
#define MEMDEV_MAJOR 251
#define MEMDEV_NUM 2
#define MEMDEV_SIZE 1024
 
struct mem_dev
{
         unsignedint size;
         char*data;
         structsemaphore sem;
};
 
 
static int mem_major = MEMDEV_MAJOR;
 
struct cdev mem_cdev;
struct mem_dev *mem_devp;
 
 
static int mem_open(struct inode *inode,struct file *filp)
{
         structmem_dev *dev;
         unsignedint num;
        
         printk("mem_open.\n");
        
         num= MINOR(inode->i_rdev);//获得次设备号
         if(num> (MEMDEV_NUM -1))          //检查次设备号有效性
                   return-ENODEV;
                  
         dev= &mem_devp[num];
         filp->private_data= dev; //将设备结构保存为私有数据
        
         return0;
}
 
static int mem_release(struct inode *inode,struct file *filp)
{
         printk("mem_release.\n");
         return0;
}
 
static ssize_t mem_read(struct file *filp,char __user *buf, size_t size, loff_t *ppos)
{
         intret = 0;
         structmem_dev *dev;
         unsignedlong p;
         unsignedlong count;
        
         printk("mem_read.\n");
        
         dev= filp->private_data;//获得设备结构
         count= size;
         p= *ppos;
        
         //检查偏移量和数据大小的有效性
         if(p> MEMDEV_SIZE)
                   return0;
         if(count> (MEMDEV_SIZE-p))
                   count= MEMDEV_SIZE - p;
                  
         if(down_interruptible(&dev->sem))//锁定互斥信号量
                  return -ERESTARTSYS;
                  
         //读取数据到用户空间
         if(copy_to_user(buf,dev->data+p, count)){
                   ret= -EFAULT;
                   printk("copyfrom user failed\n");
         }
         else{
                   *ppos+= count;
                   ret= count;
                   printk("read%d bytes from dev\n", count);
         }
        
         up(&dev->sem);//解锁互斥信号量
        
         returnret;
}
 
static ssize_t mem_write(struct file *filp,const char __user *buf, size_t size, loff_t *ppos)//注意:第二个参数和read方法不同
{
         intret = 0;
         structmem_dev *dev;
         unsignedlong p;
         unsignedlong count;
        
         printk("mem_write.\n");
        
         dev= filp->private_data;
         count= size;
         p= *ppos;
        
         if(p> MEMDEV_SIZE)
                   return0;
         if(count> (MEMDEV_SIZE-p))
                   count= MEMDEV_SIZE - p;
                  
         if(down_interruptible(&dev->sem))//锁定互斥信号量
                   return-ERESTARTSYS;
                  
         if(copy_from_user(dev->data+p,buf, count)){
                   ret= -EFAULT;
                   printk("copyfrom user failed\n");
         }
         else{
                   *ppos+= count;
                   ret= count;
                   printk("write%d bytes to dev\n", count);
         }
        
         up(&dev->sem);//解锁互斥信号量
        
         returnret;
}
 
static loff_t mem_llseek(struct file *filp,loff_t offset, int whence)
{
         intnewpos;
        
         printk("mem_llseek.\n");
        
         switch(whence)
         {
                   case0:
                            newpos= offset;
                            break;
                  
                   case1:
                            newpos= filp->f_pos + offset;
                            break;
                           
                   case2:
                            newpos= MEMDEV_SIZE - 1 + offset;
                            break;
                           
                   default:
                            return-EINVAL;
         }
        
         if((newpos<0)|| (newpos>(MEMDEV_SIZE - 1)))
                   return-EINVAL;
                  
         filp->f_pos= newpos;
         returnnewpos;
}
static const struct file_operationsmem_fops = {
         .owner= THIS_MODULE,
         .open= mem_open,
         .write= mem_write,
         .read= mem_read,
         .release= mem_release,
         .llseek= mem_llseek,
};
 
static int __init memdev_init(void)
{
         intresult;
         interr;
         inti;
        
         //申请设备号
         dev_tdevno = MKDEV(mem_major, 0);
        
         if(mem_major)
                   result= register_chrdev_region(devno, MEMDEV_NUM, "memdev");//注意静态申请的dev_t参数和动态dev_t参数的区别
         else{                                                                                                                          //静态直接传变量,动态传变量指针
                   result= alloc_chrdev_region(&devno, 0, MEMDEV_NUM, "memdev");
                   mem_major= MAJOR(devno);
         }
        
         if(result< 0){
                   printk("can'tget major devno:%d\n", mem_major);
                   returnresult;
         }
        
         //注册设备驱动
         cdev_init(&mem_cdev,&mem_fops);
         mem_cdev.owner= THIS_MODULE;
        
         err= cdev_add(&mem_cdev, MKDEV(mem_major, 0), MEMDEV_NUM);//如果有N个设备就要添加N个设备号
         if(err)
                   printk("addcdev faild,err is %d\n", err);
                  
         //分配设备内存
         mem_devp= kmalloc(MEMDEV_NUM*(sizeof(struct mem_dev)), GFP_KERNEL);
         if(!mem_devp){
                    result = - ENOMEM;
                    goto fail_malloc;
         }
         memset(mem_devp,0, MEMDEV_NUM*(sizeof(struct mem_dev)));
        
        
         for(i=0;i<MEMDEV_NUM; i++){
                   mem_devp[i].size= MEMDEV_SIZE;
                   mem_devp[i].data= kmalloc(MEMDEV_SIZE, GFP_KERNEL);
                   memset(mem_devp[i].data,0, MEMDEV_SIZE);
                   init_MUTEX(&mem_devp[i].sem);//初始化互斥锁
         }
        
         returnresult;
        
fail_malloc:
         unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM);
         returnresult;
}
 
static void memdev_exit(void)
{
         cdev_del(&mem_cdev);
         unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM);//注意释放的设备号个数一定要和申请的设备号个数保存一致
                                                                                                                         //否则会导致设备号资源流失
         printk("memdev_exit\n");
}
 
module_init(memdev_init);
module_exit(memdev_exit);
 
MODULE_AUTHOR("Y-Kee");
MODULE_LICENSE("GPL");
 


三、疑点难点

 

1.

__init

__initdata

__exit

__exitdata

         仅用于模块初始化或清除阶段的函数(__init和__exit)和数据(__initdata和__exitdata)标记。标记为初始化项目会在初始化结束后丢弃;而退出在内核未被配置为可卸载模块的情况下被简单的丢弃。被标记为__exit的函数只能在模块卸载或者系统关闭时被调用,其他任何用法都是错误的。内核通过对应的目标对象放置在可执行文件的特殊ELF段中而让这些标记起作用。

 

2.static

         初始化函数应该被声明为static,因为这种函数在特定文件之外没有其他意义。因为一个模块函数要对内核其他部分课件,则必须显示导出,因此这并不是什么强制性规则。

 

3. struct module *owner

         内核使用这个字段以避免在模块操作正在使用时卸载该模块。几乎在所有的情况下,该成员都会被初始化为THIS_MODULE。它是定义在<linux/module.h>中的一个宏。

 

4 __user

         我们会注意到许多参数包括含有__user字串,它其实是一种形式的文档而已,表面指针是一个用户指针,因此不能被直接用。对通常的编译来讲,__user没有任何效果,但是可由外部检查软件使用,用来寻找对用户空间地址错误使用。

linux内核部件分析(十)——设备驱动模型之class 前面看过了设备驱动模型中的bus、device、driver,这三种都是有迹可循的。其中bus代表实际的总线,device代表实际的设备和接口,而driver则对应存在的驱动。但本节要介绍的class,是设备类,完全是抽象出来的概念,没有对应的实体。所谓设备类,是指提供的用户接口 阅读详情

相关推荐

memdev设备驱动

  memdev.h如下: #ifndef _MEMDEV_H_ #define _MEMDEV_H_ #ifndef MEMDEV_MAJOR #define MEMDEV_MAJOR 251 /* 预设的mem的主设备号 */ #endif #ifndef MEMDEV_NR_DEVS #define MEMDEV_NR_DEVS 2 /* 设备数 */ #endif #ifndef MEMDEV_SIZE #define MEMDEV_SIZE 4096 #endif

健身房 273

Linux设备驱动工程师的学习之路:简单字符设备驱动程序编程

字符设备驱动程序负责管理对这些设备的访问,并提供读取和写入设备的功能。这个简单字符设备驱动程序只是一个入门级的示例,但它涵盖了字符设备驱动程序的基本操作。这个简单字符设备驱动程序定义了一些基本的操作,包括设备打开、设备关闭、设备读取和设备写入。这个简单字符设备驱动程序定义了一些基本的操作,包括设备打开、设备关闭、设备读取和设备写入。要编译并加载这个字符设备驱动程序,您需要在Linux环境中安装必要的开发工具,包括GCC和内核头文件。函数注册字符设备,并将设备操作与设备文件关联起来。

BrchCpp的博客 135

Linux设备驱动(一)_linux设备驱动 csdn

你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!更多资料点击此处获qu!!

2401_84009130的博客 930

字符设备驱动 (五)设备类概念

设备类概念 class 是设备类,它是一个抽象的概念,没有对应的实体。提供给用户接口相似的一类设备 的集合 使用命令查看当前系统中使用的设备类$ ls /sys/class #/sys/class路径下的目录表示系统当前存在的设备类 生成字符类设备节点 函数class_create创建class类文件struct class // 在 include/linux/device.h...

misconceptions的博客 492

20_给字符设备添加设备节点

本章目标:在第19章的基础上,给cdev字符类设备添加设备节点,再编写应用程序访问设备节点。 1. 代码设计思路 1.1 字符设备驱动设计流程 (1)模块内部新建class类型结构体变量,使用class_creat来完成初始化: static struct class *my_class; my_class = class_create(THIS_MODULE,DEVICE_NAME); (...

snaking616 625

LinuxLinux设备驱动开发详解:基于最新的Linux 4.0内核

1 Linux设备驱动概述及开发环境构建 1.1 设备驱动的作用 驱使硬件设备行动 1.2 无操作系统时的设备驱动 典型架构:一个无限循环中夹杂着对设备中断的检测或者对设备的轮询 1.3 有操作系统时的设备驱动 并发 、内存管理 1.4 Linux 设备驱动 1.4.1 设备的分类及特点 ● 字符设备。 ● 块设备。 ● 网络...

九牧林的博客 1万+

驱动程序开发:I2C设备驱动

使用I2C框架和字符设备框架读取I2C设备(ap3216c传感器),思路清晰!

morecrazylove的博客 3521

设备驱动程序的简介

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

主要记录嵌入式学习与开发过程。 8868

Linux设备驱动工程师之路——高级字符设备驱动程序

Linux设备驱动工程师之路——高级字符设备驱动程序 K-Style 转载请注明来自于衡阳师范学院08电2  K-Style  http://blog.csdn.net/ayangke,QQ:843308498 邮箱:yangkeemail@qq.com   高级字符设备驱动在简单字符驱动的基础上添加ioctl方法、阻塞非阻塞读写、poll方法、和自动创建设备文件的功能。   一、重要

屌丝终有逆袭日。 3445

Linux设备驱动工程师之路:高级字符设备驱动编程

字符设备驱动程序Linux内核中的一种重要组件,用于实现与字符设备的交互。高级字符设备驱动程序相比简单字符设备驱动程序更为复杂,通常包含了更多的功能和特性。我们讨论了如何初始化驱动程序并定义字符设备结构体,以及如何在用户空间中使用设备文件进行读写操作。通过以上的示例代码,我们可以实现一个基本的高级字符设备驱动程序,并在用户空间中进行读写操作。这个结构体包含了设备的重要信息,如设备号、设备名称、设备的操作函数等。一旦字符设备驱动程序初始化完成并注册成功,我们就可以在用户空间中使用设备文件进行读写操作了。

DevScribe的博客 98

linux设备驱动开发详解_Linux设备驱动概述

本篇文章将介绍Linux设备驱动开发的基本知识,带你快速拨开Linux驱动这团迷雾。文章分为如下3个小节进行讲解:1、设备驱动的概念和作用;2、在无操作系统和有操作系统的情况下设备驱动的设计开发;3、Linux设备驱动的重点、难点和学习方法。文章最后会给出一个简单的LED驱动在无操作系统和Linux操作系统下的实现示例。好,Let's go1、设备驱动的概念和作用大家都知道在Linux开发中是一个...

weixin_39785524的博客 1937

linux设备驱动介绍

.裸机设备驱动 vs linux设备驱动

weixin_49529507的博客 1434

为什么要学习 Linux 设备驱动?—— 嵌入式工程师的必经之路

• 什么是设备驱动? • 驱动工程师的就业前景和薪资 • 驱动开发 vs. 应用开发的区别 • 如何快速入门 Linux 设备驱动

qq_34954047的博客 1073

Linux字符设备驱动程序的编写框架

作者:游老师,华清远见嵌入式学院讲师。在华清远见的嵌入式培训课程中,linux自负设备驱动程序的编写是嵌入式学院嵌入式工程师职业培训班>三期课程中嵌入式linux驱动开发方面的重要内容,这里我们先对QT做一下简单介绍。一、Linux device driver 的概念 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽

farsightliuht的专栏 495

3-内核开发-第一个字符设备模块开发案例

Linux 字符设备驱动程序是一种内核模块,它允许用户空间程序与字符设备进行交互。字符设备是按字节而不是按块访问的设备。这意味着您可以将它们视为一组连续的字节。字符设备驱动程序对于 Linux 系统的正常运行至关重要。如果没有字符设备驱动程序,系统将无法与各种设备进行通信。串口驱动程序(/dev/ttyS*并口驱动程序(/dev/lp*打印机驱动程序(/dev/lp*键盘驱动程序(鼠标驱动程序(LCD 显示器驱动程序(/dev/fb*LED 驱动程序(/dev/led*

jxusthusiwen的专栏 1170
上一篇: 让secureCRT正确显示中文
下一篇: Linux设备驱动工程师之路——高级字符设备驱动程序
IT人Key
博客等级 码龄17年 228粉丝 67原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值