【驱动】——字符设备驱动程序

字符设备不得不说的那些事:

一: 设备号:主设备号,次设备号:

  数据类型 dev_t(unsigned int) 定义设备号  高12位主设备号 低20位次设备号;

二: 设备号的作用:

  应用程序通过主设备号找到驱动程序;

三:如何分配设备号:

  ①:静态分配:

    1: cat /proc/devices 查看linux系统哪个设备号没有被占用;

    2: dev_t dev_id = MKDEV(主设备号,次设备号)  根据你的设备个数分配次设备号 如果设备个数只有一个,一般此设备号从0开始;

    3: 调用 register_chrdev_region(dev_t from,unsigned count,const char *name);

      from:待申请的设备号     count:待申请的设备号数目   name:设备名称(出现在 /proc/devices)  

  ②:动态分配:

    1:调用 alloc_chrdev_region 直接向内核去申请设备号,也就是让操作系统内核帮你分配设备号。

  ③:设备号的注销:

    1:void unregister_chrdev_region(dev_t from, unsigned count);

四:重要的数据结构:

  ①:文件结构: struct file; //描述打开文件后的状态

    生命周期:当打开文件后,内核自动创建一个结构体 struct file  文件关闭之后 struct file 被自动销毁。

    重要成员:

      struct file_operations *f_op;   //指向驱动程序中实现的各个硬件操作方法;

      unsigned int f_flags;   //文件操作属性;

      loff_t f_ops;   //文件操作位置;

      void *private_data; //存放私有数据

  ②:inode 结构: struct inode; //用于记录文件物理信息

    生命周期:文件存在,内核创建;文件销毁,内核销毁对应的inode;

    重要成员:

      dev_t i_rdev; //存放设备号

      struct cdev *i_cdev;    //指向一个字符设备

    注:一个文件只有一个inode,可以有多个file;

  ③:文件操作结构: struct file_operations; //一个函数指针的集合,这些函数定义了能够对设备进行的操作 在 <linux/fs.h> 中定义

  ④:字符设备 struct cdev

五:重要的函数:

  ①:分配设备号,见【三:如何分配设备号】

  ②:注册字符设备:

    1: void cdev_init(struct cdev *cdev, struct file_operations *fops);  //将字符设备与处理函数进行绑定,把 struct file_operations 注册到内核中

    2: int cdev_add(struct cdev *dev, dev_t num, unsigned int count);  //将 dev 添加到内核的cdev的数组之中 下标是以设备号为索引!一旦完成对 cdev的注册,就等于有了一个真实的字符设备,关键这个驱动就有了,对应的操作集合 fops;

    3: void cdev_del(struct cdev *dev);    //从系统中移除一个字符设备

六:简单的设备驱动程序例子:

Makefile:

ifeq ($(KERNELRELEASE), )
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
all: clean
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    -rm -rf *.o *~ core .depend .*.cmd *.mod.c .tmp_versions Module.* Makefile.xen modules.order
clean:
    -rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.* Makefile.xen modules.order
else
MODULE_NAME=scull
    obj-m := $(MODULE_NAME).o
    $(MODULE_NAME)-objs := file_op.o my_project.o
endif

my_project.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <asm/uaccess.h>

#include "file_op.h"

uint dev_major = 60;
char device_name[20] = "ngnetboy";
dev_t scull_dev;    //设备号
static struct cdev scull_cdev;  //字符设备

struct file_operations scull_fops = {
    .owner = THIS_MODULE,
    .read = scull_read,
    .write = scull_write,
    .ioctl = scull_ioctl,
    .open = scull_open,
    .release = scull_release,
};
static int __init scull_init(void){
    int ret;

    printk("hello kernel!\n");
    // make device num
    scull_dev = MKDEV(dev_major, 0);
    // register device
    ret = register_chrdev_region(scull_dev, 1, device_name);
    if (ret < 0){
        printk("register chrdev region failed!\n");
        ret = alloc_chrdev_region(&scull_dev, 0, 1, device_name);
        dev_major = MAJOR(scull_dev);
    }
    if (ret < 0){
        printk("%s register failed!\n", device_name);
        return ret;
    }
    //scull_cdev = cdev_alloc();
    cdev_init(&scull_cdev, &scull_fops);
    ret = cdev_add(&scull_cdev, scull_dev, 1);
    if (ret < 0){
        printk("cdev add failed!\n");
        goto chr_quit1;
    }
    return 0;
chr_quit1:
    unregister_chrdev_region(scull_dev, 1);
    return ret;
}

static void __exit scull_exit(void){

    cdev_del(&scull_cdev);
    unregister_chrdev_region(scull_dev, 1);

    printk("bye kernel!\n");
}

module_init(scull_init);
module_exit(scull_exit);

MODULE_AUTHOR("ngnetboy");
MODULE_DESCRIPTION("a simple character utility for loading localities");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.0.3");

file_op.c

 1 #include <linux/fs.h>
 2 #include <asm/uaccess.h>
 3 #include <linux/init.h>
 4 #include <linux/module.h>
 5 #include <linux/types.h>
 6 #include <linux/cdev.h>
 7 #include <linux/kernel.h>
 8
 9 #include "file_op.h"
10
11 int scull_read(struct file *filep, char __user *buff, size_t count, loff_t *offp){
12     char val[20] = "this is read!";
13     int ret;
14
15     ret = copy_to_user(buff, val, count);
16     return 0;
17 }
18 int scull_write(struct file *filep, const char __user *buff, size_t count, loff_t *offp){
19     char val[20];
20     int ret;
21
22     printk("hello write!\n");
23     ret = copy_from_user(val, buff, count);
24     printk("ret :%d copy from user %s\n", ret, val);
25     return 0;
26 }
27 int scull_open(struct inode *inode, struct file *filp){
28     printk("hello open!\n");
29     return 0;
30 }
31 int scull_release(struct inode *inode, struct file *filp){
32     printk("hello release!\n");
33     return 0;
34 }
35 int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg){
36
37     return 0;
38 }

使用 mknod /dev/netboy c 60 0 即可在 /dev 下创建一个可供应用程序可用的字符设备文件结点;

七:当应用程序 open 创建的字符设备时内核和驱动都做了那些事情?

  1:当应用程序调用 open 打开设备文件(打开设备);

  2:调用 C 库的open实现,会保存 open 的系统调用号;

  3:C库的open实现调用 swi 触发一个软中断,跳转到内核态;

  4:根据系统调用号,在系统调用表中找到open对应内核系统调用实现 sys_open;

  5:调用 sys_open, sys_open 会做如下工作:

    ①:通过 inode.i_rdev 获取设备号;

    ②:根据设备号在内核cdev数组中找到对应的字符设备驱动;

    ③:然后将找到的cdev的地址赋值给 inode.i_cdev 用于缓存和别的用途;

    ④:创建 struct file 结构体内存 用于描述打开的设备文件信息;

    ⑤:根据已经获得的 cdev,从而获得其中的驱动操作集合ops();

    ⑥:将字符设备驱动的操作接口ops再赋值给 file->ops;

    ⑦:最后再调用一次 file->f_op->open = led_open;

八:四个重要的数据结构之间有什么联系?

  1:struct file_operations fops ;是由驱动开发者实现,包括相应的函数实现;并且由驱动开发者把fops注册到内核中与struct cdev 绑定到一起;

  2:使用 mknod 创建 /dev/netboy 时,struct inode node,便已被创建,并随着文件的销毁而被释放;

  3:当驱动程序被调用,用户程序调用open打开字符设备的时候,struct file 便被创建,内核通过设备号找到对应的字符设备【struct cdev】,然后把fops赋值给 struct file;

    

时间: 2024-10-06 02:45:12

【驱动】——字符设备驱动程序的相关文章

0915-----Linux设备驱动 学习笔记----------一个简单的字符设备驱动程序

0.前言 研究生生活一切都在步入正轨,我也开始了新的学习,因为实在不想搞存储,所以就决定跟师兄学习设备驱动,看了两星期书,终于有点头绪了,开始记录吧! 1.准备工作 a)查看内核版本 uname -r b)安装内核源码树(http://www.cnblogs.com/Jezze/archive/2011/12/23/2299871.html) 在www.linux.org上下载源码编译,这里是.xz格式,需要安装解压工具,xz-utils: 解压方法示例:xz -d linux-3.1-rc4.

Linux嵌入式驱动学习之路(二十一)字符设备驱动程序总结和块设备驱动程序的引入

字符设备驱动程序 应用程序是调用C库中的open read write等函数.而为了操作硬件,所以引入了驱动模块. 构建一个简单的驱动,有一下步骤. 1. 创建file_operations 2. 申请设备号 3. 注册字符设备驱动, 4. 驱动入口 5. 驱动出口 检查数据是否到来的方式: 1. 查询方式 2. 休眠唤醒方式 如果设备出现异常而无法唤醒时,则将永远处于休眠状态. 3. poll机制 如果没有被唤醒,则在一定时间内可自己唤醒. 4. 异步通知(信号) 而以上的几种方式通用性不高,

linux 驱动学习(一)简单的字符设备驱动程序

linux 系统将设备分为三种类型:字符设备.块设备和网络接口设备. 文章将先给出字符设备驱动程序,参照程序记录知识点,可能会不全,以后会慢慢加 .知识点记录完成后,会贴出字符设备驱动程序的测试程序并记录测试过程. 注释版 1 #include "linux/kernel.h" //内核头文件,含有一些内核常用函数的原形定义 2 #include "linux/module.h" //包含大量加载模块需要的函数和符号的定义 3 #include "linu

字符设备驱动程序的使用

1.编译.安装驱动linux系统中,驱动程序通常采用内核模块的程序结构来进行编码,因此,编译.安装一个驱动程序,其实质就是编译.安装一个内核模块.将文件memdev.c makefile 放入虚拟机中,make 得到.ko文件cp .ko rootfs启动开发板 insmod *.ko2.创建设备文件通过字符设备文件,应用程序可以使用相应的字符设备驱动程序来控制字符设备.创建字符设备文件的方法:a.使用mkmod命令 mknod /dev/文件名 c 主设备号(将驱动和设备文件关联) 次设备号m

【Linux 驱动】设备驱动程序再理解

学习设备驱动编程也有一段时间了,也写过了几个驱动程序,因此有对设备驱动程序有了一些新的理解和认识,总结一下.学习设备驱动编程也有一段时间了,也写过了几个驱动程序,因此有对设备驱动程序有了一些新的理解和认识,总结一下. ★什么是驱动程序 刚开始学习设备驱动程序的时候,产生了许多的问题.什么是驱动程序?驱动程序是干嘛的?它是如何工作的?它又是如何跟操作系统联系起来的?一系列的问题,现在有些地方还是不一定清楚,但是相比起刚开始的那个阶段,感觉自己还是清楚了很多. 设备驱动程序说白了(实质)就是为应用程

20150216简单的Linux字符设备驱动程序

20150216简单的Linux字符设备驱动程序 2015-02-16 李海沿 关于字符设备驱动程序详细的知识点,本文就不再介绍了,很多同志,看了知识点,还是一头雾水,写不出来,所以,本文从实战出发,带领各位同胞们来实现一个字符设备驱动程序,改程序可作为字符设备的通用模板. 好了废话不多说,先上驱动程序,在驱动程序中加入详细注释: 1 /****************************** 2 linux 字符设备驱动程序 3 *****************************/

Linux 简单字符设备驱动程序 (自顶向下)

第零章:扯扯淡 特此总结一下写的一个简单字符设备驱动程序的过程,我要强调一下“自顶向下”这个介绍方法,因为我觉得这样更容易让没有接触过设备驱动程序的童鞋更容易理解,“自顶向下”最初从<计算机网络 自顶向下方法>这本书学到的,我觉得有时候这是一种很好的方式. 第一章:测试程序 咦?你怎么跟别人的思路不一样???自顶向下嘛,我就直接从测试程序来说啦,这样那个不是更熟悉吗?看看下面的测试程序的代码,是不是很熟悉? 1 #include <stdio.h> 2 #include <u

浅析Linux字符设备驱动程序内核机制

前段时间在学习linux设备驱动的时候,看了陈学松著的<深入Linux设备驱动程序内核机制>一书. 说实话.这是一本非常好的书,作者不但给出了在设备驱动程序开发过程中的所须要的知识点(如对应的函数和数据结构),还深入到linux内核里去分析了这些函数或数据结构的原理.对设备驱动开发的整个过程和原理都分析的非常到位.但可能是因为知识点太多.原理也比較深的原因,这本书在知识点的排版上跨度有些大.所以读起来显得有点吃力,可是假设第一遍看的比較认真的话,再回头看第二次就真的可以非常好地理解作者的写作思

linux字符设备驱动程序源码分析

本文主要分析linux-2.6.28内核版本的字符设备抽象层源码文件char_dev.c.该文件代码量不大,但其为linux应用程序访问实际字符型硬件设备搭建了桥梁,进一步限定了linux字符设备驱动的设计框架. void __init chrdev_init(void) {  cdev_map = kobj_map_init(base_probe, &chrdevs_lock);  bdi_init(&directly_mappable_cdev_bdi); } 该函数在系统初始化启动时

简单字符设备驱动程序

linux驱动程序开发步骤 1)查看原理图.数据手册,了解设备的操作方法. 2)在内核中找到相近的驱动程序,以它为模板进行开发,有时候需要从零开始. 3)实现驱动程序的初始化:比如向内核注册驱动程序,这样应用程序传入文件名时,内核才能找到相应的驱动程序. 4)设计所要实现的操作,比如open,read,write,close. 5)实现中断服务(不是必需的) 6)编译该驱动程序到中,或者用insmod命令加载. 7)测试驱动程序.  驱动框架 应用程序API接口read,open,write 是