精华内容
参与话题
问答
  • Linux设备驱动程序

    2014-01-22 09:22:29
    设备驱动程序简介 ·机制和策略 ·内核划分 ·内核模块可加载性 ·Linux内核驱动之设备和模块的分类   构造和运行模块 ·Ubuntu编译内核 ·Linux内核驱动之Hello World 模块 ·Linux...

    设备驱动程序简介

    ·机制和策略

    ·内核划分

    ·内核模块可加载性

    ·Linux内核驱动之设备和模块的分类

      

    构造和运行模块

    ·Ubuntu编译内核

    ·Linux内核驱动之Hello World 模块

    ·Linux内核驱动之内核模块相比于应用程序

    ·Linux内核驱动之内核模块编译和加载

    ·Linux内核驱动之内核符号导出

    ·Linux内核驱动之预备知识

     ·Linux内核驱动之初始化和关停

    ·Linux内核驱动之模块参数

      

    字符设备驱动程序

    ·Linux内核驱动之主次编号

    ·Linux内核驱动之一些重要数据结构

    ·Linux内核驱动之字符设备注册

    ·Linux内核驱动之自动创建设备文件

    ·Linux内核驱动之open  release

    ·Linux内核驱动之readwrite

    ·Linux内核驱动之Proc文件系统

     

     并发和竞态

    ·Linux内核驱动之信号量和互斥锁

    ·Linux内核驱动之读者/写者旗标

    ·Linux内核驱动之自旋锁

    ·Linux内核驱动之原子变量

    ·Linux内核驱动之位操作

      

    高级字符驱动程序操作

    ·Linux内核驱动之ioctl接口

    ·Linux内核驱动之等待队列

    ·Linux内核驱动之pollselect

    ·Linux内核驱动之llseek 实现

      

    时间、延迟及延缓操作

    Linux内核驱动之延时

    ·Linux内核驱动之定时器

    ·Linux内核驱动之Tasklets机制

     
     分配内存

    ·Linux内核驱动之内存分配

     与硬件通信

    ·Linux内核驱动之I/O内存

      
    中断处理

    ·Linux内核驱动之中断处理

    ·Linux内核驱动之中断处理前和后半部

    ·Linux内核驱动之内核链表

     
      Linux设备模型

    ·linux内核驱动之设备模型抽象大体分类

    ·Linux内核驱动之Sysfs文件系统

    ·Linux内核驱动之Kobject

    ·Linux内核驱动之kset

    ·Linux内核驱动之热插拔事件

    ·Linux内核驱动之总线

    ·Linux内核驱动之设备

    ·Linux内核驱动之驱动

    ·Linux内核驱动之paltform总线

      

     Input输入子系统

    Linux内核驱动之input子系统介绍

    Linux内核驱动之Input子系统设备驱动层

    input子系统 软件设计流程及相关API

     

    展开全文
  • 这一部分主要是用来介绍Linux设备驱动程序的一些基本概念,包括:Linux设备驱动程序的作用、内核功能的划分以及设备和模块的分类。 Linux设备驱动程序的作用 设备驱动程序就像一个个的“黑盒子”,使某个特定硬件...

      这一部分主要是用来介绍Linux设备驱动程序的一些基本概念,包括:Linux设备驱动程序的作用、内核功能的划分以及设备和模块的分类。

    Linux设备驱动程序的作用

       设备驱动程序就像一个个的“黑盒子”,使某个特定硬件响应一个定义良好的内部编程接口,这些操作完全隐藏了设备的工作细节。用户的操作通过一组标准化的调用执行,而这些调用独立于特定的驱动程序。将这些调用映射到作用于实际硬件的设备特有操作上,则是设备驱动程序的任务。
       简洁的来说设备驱动程序的作用在于提供机制(需要提供什么功能),而不在于提供策略(这些功能怎么使用)。
    拓展:
       编写驱动程序应该注意这个基本概念:编写内核代码来存取硬件, 但是不能强加特别的策略给用户, 因为不同的用户有不同的需求. 驱动程序应当做到使硬件可用, 将所有关于如何使用硬件的事情留给应用程序。

       驱动程序设计要考虑的三个方面:

    • 提供给用户尽可能多的选项
    • 编写驱动程序要占用的时间
    • 尽量保持程序简单避免产生过多的错误

    内核功能的划分

    内核功能可以主要划分为以下五个部分:

    • 进程管理
      内核负责创建和销毁进程, 并处理它们与外部世界的联系(输入和输出), 更通常地, 内核的进程管理活动实现了多个进程在一个单个或者几个 CPU 之上的抽象.

    • 内存管理
      计算机的内存是主要的资源, 处理它所用的策略对系统性能是至关重要的. 内核为所有进程的每一个都在有限的可用资源上建立了一个虚拟地址空间.

    • 文件系统
      Unix 在很大程度上基于文件系统的概念; 几乎 Unix 中的任何东西都可看作一个文件. 内核在非结构化的硬件之上建立了一个结构化的文件系统, 结果是文件的抽象非常多地在整个系统中应用.

    • 设备控制
      几乎每个系统操作最终都映射到一个物理设备上. 除了处理器, 内存和非常少的别的实体之外, 全部中的任何设备控制操作都由特定于要寻址的设备相关的代码来进行. 这些代码称为设备驱动。内核中必须嵌入系统中出现的每个外设的驱动, 从硬盘驱动到键盘和磁带驱动器。

    • 网络功能
      网络必须由操作系统来管理, 因为大部分网络操作不是特定于某一个进程, 另外, 所有的路由和地址解析问题都在内核中实现。

      设备和模块分类

      以 Linux 的方式看待设备可区分为 3 种基本设备类型. 每个模块常常实现 3 种类型中的 1 种, 因此可分类成字符模块, 块模块, 或者一个网络模块。
      三种类型的设备:

    • 字符设备
      字符设备是个能像字节流(类似文件)一样被访问的设备,由字符设备驱动来实现这种特性。字符设备驱动通常至少实现open,close,read,write系统调用。

    • 块设备
      一个块设备驱动程序主要通过传输固定大小的数据来访问设备。块设备和字符设备的区别仅仅在于内核管理数据的方式,也就是内核及驱动程序之间的软件接口,而这些不同对用户来讲是透明的。在内核中,和字符取得相比,块设备具有完全不同的接口。

    • 网络接口设备
      任何网络事物都经过一个网络接口形成,即一个能够和其它主机交换数据的设备。它可以是个硬件设备,但也可能是个纯软件设备。访问网络接口的方法仍然是给它们分配一个唯一的名字(比如eth0),但这个名字在文件系统中不存在对应的节点。内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包传输相关的函数而不是read、write等。

      安全问题

       任何从内核中得到的内存,都必须在提供给用户进程或者设备之前清零或者以其它方式初始化,否则就可能发生信息泄露(如数据和密码泄露等)。
       一个经过恶意修改过的内核可能会允许任何人装载内核,所有,下载内核的地址应该选择正规网站。

    展开全文
  • 深入Linux设备驱动程序内核机制 你懂的 好书 今年新书 驱动
  • 对于想入门Linux设备驱动程序的人来说,字符设备驱动往往是最简单的了解和实践的最佳选择,这里就实现一个简单的字符驱动,以便于大家了解字符设备驱动程序。 字符设备驱动程序 这里就我实现的字符设备驱动程序的源...

      对于想入门Linux设备驱动程序的人来说,字符设备驱动往往是最简单的了解和实践的最佳选择,这里就实现一个简单的字符驱动,以便于大家了解字符设备驱动程序。

    字符设备驱动程序

      这里就我实现的字符设备驱动程序的源代码来讲解,这只是最简单的实现,便于大家了解,因此并没有涉及到锁,休眠等操作,不过在实际的字符设备驱动程序中,这些操作也是很常见的。

    char.c源代码

    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/cdev.h>
    #include <linux/slab.h>
    #include <linux/uaccess.h>
     
    #include <linux/device.h>
     
    #define MAX_SIZE    0x1000
    #define MEM_CLEAR         0x1
     
    static struct class *class;
    static struct device *dev;
     
     
    struct chardev_dev           //定义设备
    {
        struct cdev cdev;
        unsigned char mem[MAX_SIZE];
    };
     
    struct chardev_dev *devp;    
    dev_t devno;                      //设备号
     
     
    static int chardev_open(struct inode *inode, struct file *filp)   //open函数
    {
        filp->private_data = devp;
        return 0;
    }
     
     
    static int chardev_release(struct inode *inode, struct file *filp)  //release函数
    {
        return 0;
    }
     
     
    static long chardev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)   //ioctl函数
    {
        struct chardev_dev *dev = filp->private_data;
        
        switch (cmd)
        {
        case MEM_CLEAR:
            memset(dev->mem, 0, MAX_SIZE);
            printk(KERN_INFO "chardev is set to zero\n");
            break;
        
        default:
            return -EINVAL;
        }
        
        return 0;
    }
     
    static ssize_t chardev_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)    //read函数
    {
        unsigned long p = *ppos;
        unsigned int count = size;
        int ret = 0;
        struct chardev_dev *dev = filp->private_data;
        
        if (p >= MAX_SIZE)
            return -EINVAL;
        
        if (count > MAX_SIZE - p)
            count = MAX_SIZE - p;
            
        if (copy_to_user(buf, dev->mem + p, count))                             //从内核空间得到数据
            ret = -EINVAL;
        else
        {
            *ppos += count;
            ret = count;
            printk(KERN_INFO "read %u byte(s) from %lu\n", count, p);
        }
        
        return ret;
    }
     
     
    static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t size,             //write函数
                        loff_t *ppos)
    {
        unsigned long p = *ppos;
        unsigned int count = size;
        int ret = 0;
        struct chardev_dev *dev = filp->private_data;
        
        if (p >= MAX_SIZE)
            return -EINVAL;
        if (count > MAX_SIZE - p)
            count = MAX_SIZE - p;
            
        if (copy_from_user(dev->mem + p, buf, count))                //从用户空间得到写入的数据
            ret = -EINVAL;
        else                    
        {
            *ppos += count;
            ret = count;
            printk(KERN_INFO "write %u byte(s) from %lu\n", count, p);
        }
        
        return ret;
    }
     
     
    static loff_t chardev_llseek(struct file *filp, loff_t offset, int orig)                     //llseek函数
    {
        loff_t ret = 0;
        
        switch (orig)                                     //判断文件指针的位置,确定从何开始读写
        {
        case 0:
            if (offset < 0)
            {
                ret = -EINVAL;
                break;
            }
            if (offset > MAX_SIZE)
            {
                ret = -EINVAL;
                break;
            }
            filp->f_pos = offset;
            ret = filp->f_pos;
            break;
            
        case 1:
            if ((filp->f_pos + offset) < 0 )
            {
                ret = -EINVAL;
                break;
            }
            
            if ((filp->f_pos + offset) > MAX_SIZE)
            {
                ret = -EINVAL;
                break;
            }
            filp->f_pos += offset;
            ret = filp->f_pos;
            break;
            
        default:
            ret = -EINVAL;
            break;
        }
        
        return ret;
    }
     
     
    static const struct file_operations chardev_fops =                  //字符设备操作函数定义
    {
        .owner  =  THIS_MODULE,
        .llseek =  chardev_llseek,
        .read   =  chardev_read,
        .write  =  chardev_write,
        .unlocked_ioctl = chardev_ioctl,
        .open   =  chardev_open,
        .release = chardev_release,
    };
     
    static char *chardev_devnode(struct device *dev, umode_t *mode)
    {
        if (mode)
            *mode = 0666;
     
        return NULL;
    }
     
    static int __init chardev_init(void)                              //初始化,入口函数
    {
        int ret;
        int err;
        
        ret = alloc_chrdev_region(&devno, 0, 1, "chardev");            //动态申请设备号
        if (ret < 0)
            return ret;
            
        devp = kzalloc(sizeof(struct chardev_dev), GFP_KERNEL);         //分配内存空间
        if (!devp)
        {
            ret = -ENOMEM;
            goto fail_malloc;
        }
        
        class = class_create(NULL, "chardev");                          //创建类节点
        if (IS_ERR(class))
        {
            ret = PTR_ERR(class);
            printk(KERN_ERR "class create error %d\n", ret);
            goto fail_malloc;
        }
        
        class->devnode = chardev_devnode;                              //创建设备节点
        dev = device_create(class, NULL, devno, NULL, "chardev");
        if (IS_ERR(class))
        {
            ret = PTR_ERR(dev);
            printk(KERN_ERR "device create error %d\n", ret);
            goto bad_device_create;
        }
        
        
        cdev_init(&devp->cdev, &chardev_fops);                          //绑定操作函数的结构体
        devp->cdev.owner = THIS_MODULE;
        err = cdev_add(&devp->cdev, devno, 1);                          //调用cdev_add函数将cdev结构体注册到内核
        if (err)
            printk(KERN_NOTICE "Error %d adding chardev", err);
            
        return 0;
        
        
    bad_device_create:
        class_destroy(class);
    fail_malloc:
        unregister_chrdev_region(devno, 1);
        return ret;
    }
     
     
    static void __exit chardev_exit(void)                             //注销
    {
        device_destroy(class, devno);
        class_destroy(class);
        cdev_del(&devp->cdev);
        kfree(devp);
        unregister_chrdev_region(devno, 1);
    }
     
    module_init(chardev_init);                                      //加载模块
    module_exit(chardev_exit);                                      //退出模块
     
    MODULE_AUTHOR("lql");
    MODULE_LICENSE("GPL");
    

    Makefile文件

    ifneq ($(KERNELRELEASE),)
    # call from kernel build system
    	obj-m	:= char.o
    else
    	KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    	PWD       := $(shell pwd)
    
    modules:
    	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    
    endif
    
    clean:
    	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
    
    

    注意哦,记得make之后把模块加载进去,不然的话没有效果的。

    用户空间操作

    既然在内核空间实现了字符设备驱动,那么想要测试效果的话,可以写两个读写函数来测试:

    read函数

    #include<sys/types.h>
    #include<unistd.h>
    #include<sys/stat.h>
    #include<stdio.h>
    #include<fcntl.h>
    #include<string.h>
    int main()
    {
        int fd,i;
        char msg[101];
        fd= open("/dev/chardev",O_RDWR,S_IRUSR|S_IWUSR);
        if(fd!=-1)
        {
                for(i=0;i<101;i++)
                    msg[i]='\0';
                read(fd,msg,100);
                printf("%s\n",msg);
        }
        else
        {
            printf("device open failure,%d\n",fd);
        }
        return 0;
    }
    

    write函数

    #include<sys/types.h>
    #include<unistd.h>
    #include<sys/stat.h>
    #include<stdio.h>
    #include<fcntl.h>
    #include<string.h>
    int main()
    {
        int fd;
        char msg[100];
        fd= open("/dev/chardev",O_RDWR,S_IRUSR|S_IWUSR);
        if(fd!=-1)
        {
            while(1)
            {
                printf("Please input the globar:(input quit go out)\n");
                scanf("%s",msg);
                if(strcmp(msg,"quit")==0)
                  {
                    close(fd);
                    break;
                  }
                write(fd,msg,strlen(msg));
                
            }
        }
        else
        {
            printf("device open failure\n");
        }
        return 0;
    }
    

      这两个c文件正常编译输出,就可以得到对应的实现结果。

    展开全文
  • Linux设备驱动程序教程将为您提供有关如何为Linux操作系统编写设备驱动程序的所有必要信息。 本文包含一个易于遵循的实用Linux驱动程序开发示例。 我们将讨论以下内容: 内核日志系统 如何使用角色设备 如何使用...

    翻译来自:
    https://www.apriorit.com/dev-blog/195-simple-driver-for-linux-os
    代码下载

    此Linux设备驱动程序教程将为您提供有关如何为Linux操作系统编写设备驱动程序的所有必要信息。 本文包含一个易于遵循的实用Linux驱动程序开发示例。 我们将讨论以下内容:

    • 内核日志系统
    • 如何使用角色设备
    • 如何使用内核中的用户级内存

    我们将使用Linux内核版本2.6.32。 我们可以使用更新的版本,但是它们的API可能已被修改,因此可能与我们的示例和构建系统中使用的API不同。 学习本教程后,您将熟悉为Linux操作系统编写设备驱动程序或内核模块的过程。

    概述

    Linux有一个单片内核。 因此,为Linux编写设备驱动程序需要与内核进行组合编译。 另一种方法是将驱动程序实现为内核模块,在这种情况下,您无需重新编译内核即可添加其他驱动程序。 我们将关注第二个选项:内核模块。

    在其基础上,模块是专门设计的目标文件。 使用模块时,Linux会将它们加载到其内核空间,从而将它们链接到内核。 Linux内核是使用C编程语言和Assembler开发的。 C实现了内核的主要部分,而Assembler实现了依赖于体系结构的部分。 不幸的是,这些是我们用于编写Linux设备驱动程序的唯一两种语言。 我们不能使用用于Microsoft Windows操作系统内核的C ++,因为Linux内核源代码的某些部分 - 特定的头文件 - 可能包含来自C ++的关键字(例如, delete或new ),而在Assembler中我们可能会遇到诸如’ : : ‘词汇。

    我们在内核上下文中运行模块代码。 这需要开发人员非常专注,因为它需要承担额外的责任:如果开发人员在实现用户级应用程序时出错,在大多数情况下这不会导致用户应用程序之外的问题; 但是如果开发人员在实现内核模块时出错,后果将是系统级别的问题。 幸运的是,Linux内核有一个很好的功能,可以抵御模块代码中的错误。 当内核遇到非严重错误(例如,空指针解除引用)时,您将看到oops消息(Linux操作期间无意义的故障称为oops ),之后将卸载故障模块,允许内核和其他模块像往常一样工作。 此外,您还可以在内核日志中找到准确描述此错误的记录。 但请注意,不建议在oops消息之后继续工作,因为这样做可能会导致不稳定和内核恐慌。

    内核及其模块本质上代表一个程序模块 - 因此请记住,单个程序模块使用单个全局命名空间。 为了最小化它,您必须观察模块导出的内容:导出的全局字符必须唯一命名(常用的解决方法是简单地使用将字符导出为前缀的模块的名称)并且必须剪切到最低限度。

    装载和卸载模块

    要创建一个简单的示例模块,我们不需要做太多工作。 这里有一些代码可以证明这一点:

    #include <linux/init.h>
    #include <linux/module.h>
    
    static int my_init(void)
    {
                           return  0;
    }
    
    static void my_exit(void)
    {
                           return;
    }
    
    module_init(my_init);
    module_exit(my_exit);

    这个模块唯一做的两件事就是加载和卸载自己。 要加载Linux驱动程序,我们调用my_init函数,并卸载它,我们调用my_exit函数。 module_init和module_exit宏通知内核有关驱动程序的加载和卸载。 my_init和my_exit函数必须具有相同的签名,这些签名必须完全如下:

    int init(void);
    void exit(void);

    如果模块需要某个内核版本并且必须包含有关版本的信息,我们需要链接linux / module.h头文件。 尝试加载为另一个内核版本构建的模块将导致Linux操作系统禁止其加载。 出现这种情况的原因是:内核API的更新经常被释放,当您调用其签名已更改的模块函数时,会对整个堆栈造成损害。 module_init和module_exit宏在linux / init.h头文件中声明。

    注册角色设备

    上面的示例模块非常简单; 现在我们将开始处理更复杂的事情。 然而,这个简短的Linux内核驱动程序教程的目的之一是展示如何使用登录内核以及如何与设备文件交互。 这些工具可能很简单,但它们可以为任何驱动程序派上用场,并且在某种程度上,它们使内核模式开发过程更加丰富。

    首先,这里有一些有关设备文件的有用信息。 通常,您可以在/ dev文件夹中找到设备文件。 它们促进了用户和内核代码之间的交互。 如果内核必须接收任何内容,您只需将其写入设备文件即可将其传递给提供此文件的模块; 从设备文件中读取的任何内容都来自提供此文件的模块。 我们可以将设备文件分为两组:字符文件和块文件。 字符文件是非缓冲的,而块文件是缓冲的。 正如其名称所暗示的那样,字符文件允许您逐个字符地读取和写入数据,而块文件允许您只写入整个数据块。 我们将讨论块文件超出本文的范围,并将直接获得字符文件。

    Linux系统有一种通过主设备号识别设备文件的方法, 主设备号识别服务设备文件或一组设备的模块,以及次要设备号 ,用于识别主设备号指定的一组设备中的特定设备。 在驱动程序代码中,我们可以将这些数字定义为常量,也可以动态分配它们。 如果已经使用了定义为常量的数字,系统将返回错误。 当动态分配一个数字时,该函数保留该数字以禁止其他任何数字使用它。

    指定设备的名称

    下面引用的函数用于注册字符设备:

    int register_chrdev (unsigned int   major,
                         const char *   name,
                         const struct   fops);
                         file_operations *

    在这里,我们指定要注册它的设备的名称和主要编号,之后将链接设备和file_operations结构。 如果我们为主参数指定零,该函数将自己分配一个主设备号(即它返回的值)。 如果返回的值为零,则表示成功,而负数表示错误。 两个设备编号均在0-255范围内指定。

    我们将设备名称作为name参数的字符串值传递(如果模块注册单个设备,则此字符串也可以传递模块的名称)。 然后,我们使用此字符串来标识/ sys / devices文件中的设备。 读取,写入和保存等设备文件操作由存储在file_operations结构中的函数指针处理。 这些函数由模块实现,并且指向标识该模块的module结构的指针也存储在file_operations结构中。 在这里你可以看到2.6.32内核版本结构:

    
    struct file_operations {
           struct module *owner;
           loff_t (*llseek) (struct file *, loff_t, int);
           ssize_t (*read) (struct file *, char *, size_t, loff_t *);
           ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
           int (*readdir) (struct file *, void *, filldir_t);
           unsigned int (*poll) (struct file *, struct poll_table_struct *);
           int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
           int (*mmap) (struct file *, struct vm_area_struct *);
           int (*open) (struct inode *, struct file *);
           int (*flush) (struct file *);
           int (*release) (struct inode *, struct file *);
           int (*fsync) (struct file *, struct dentry *, int datasync);
           int (*fasync) (int, struct file *, int);
           int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*readv) (struct file *, const struct iovec *, unsigned long,
              loff_t *);
        ssize_t (*writev) (struct file *, const struct iovec *, unsigned long,
              loff_t *);
        };

    file_operations结构

    如果file_operations结构包含一些不需要的函数,您仍然可以使用该文件而不实现它们。 指向未实现函数的指针可以简单地设置为零。 之后,系统将负责该功能的实现并使其正常运行。 在我们的例子中,我们将实现read函数。

    由于我们要确保只使用我们的Linux驱动程序操作单一类型的设备,因此我们的file_operations结构将是全局和静态的。 相应地,在它创建之后,我们需要静态填充它。 在这里你可以看到这是如何完成的:

    
    static struct file_operations simple_driver_fops = 
    {
        .owner   = THIS_MODULE,
        .read    = device_file_read,
    };

    THIS_MODULE宏的声明包含在linux / module.h头文件中。 我们将宏转换为指向所需模块的模块结构的指针。 稍后,我们将用原型编写函数体,但是现在我们只有指向它的指针,即device_file_read 。

    ssize_t device_file_read (struct file *, char *, size_t, loff_t *);

    file_operations结构允许我们编写几个函数来执行和撤销设备文件的注册。

    static int device_file_major_number = 0;
    static const char device_name[] = "Simple-driver";
    static int register_device(void)
    {
            int result = 0;
            printk( KERN_NOTICE "Simple-driver: register_device() is called." );
            result = register_chrdev( 0, device_name, &simple_driver_fops );
            if( result < 0 )
            {
                printk( KERN_WARNING "Simple-driver:  can\'t register character device with errorcode = %i", result );
                return result;
            }
            device_file_major_number = result;
            printk( KERN_NOTICE "Simple-driver: registered character device with major number = %i and minor numbers 0...255"
                 , device_file_major_number );
            return 0;
    }

    device_file_major _number是一个包含主设备号的全局变量。 当驱动程序的生命周期到期时,此全局变量将撤消设备文件的注册。

    printk Fucntion

    我们已经列出并提到了几乎所有的功能,最后一个是printk功能。 这个函数的声明包含在linux / kernel.h文件中,它的任务很简单:记录内核消息。 毫无疑问,请注意KERN_NOTICE和KERN_WARNING前缀,这些前缀出现在printk的所有列出的格式字符串中。 您可能已经猜到, NOTICE和WARNING表示消息的优先级。 级别从最无关紧要的KERN_DEBUG到关键的KERN_EMERG ,提醒内核不稳定。 这是printk函数和printf库函数之间的唯一区别。

    printk函数形成一个字符串,我们将其写入循环缓冲区, klog守护程序将其读取并将其发送到系统日志。 printk函数的实现允许从内核中的任何地方调用它。 最糟糕的情况是循环缓冲区溢出,这意味着最旧的消息不会记录在日志中。

    下一步是编写一个函数来恢复设备文件的注册。 如果成功注册了设备文件,则device_file_major_number的值将不为零。 这允许我们使用nregister_chrdev function撤销文件的注册,我们在linux / fs.h文件中声明了该nregister_chrdev function 。 主设备号是此函数的第一个参数,后跟包含设备名称的字符串。 register_chrdev和unresister_chrdev函数以类似的方式起作用。

    要注册设备,我们使用以下代码:

    
    void unregister_device(void)
    {
        printk( KERN_NOTICE "Simple-driver: unregister_device() is called" );
        if(device_file_major_number != 0)
        {
            unregister_chrdev(device_file_major_number, device_name);
        }
    }

    使用在用户模式下分配的内存

    我们要编写的函数将从设备中读取字符。 此函数的签名必须适合file_operations结构中的签名:

    ssize_t (*read) (struct file *, char *, size_t, loff_t *);

    让我们看看第一个参数,即指向file结构的指针。 此file结构允许我们获取有关我们正在使用的文件的必要信息,有关此当前文件的私有数据的详细信息,等等。 已读取的数据使用第二个参数(即缓冲区)分配给用户空间。 读取的字节数在第三个参数中定义,我们从第四个参数中定义的某个偏移量开始读取字节。 执行该函数后,必须返回已成功读取的字节数,之后必须刷新偏移量。

    用户在用户模式地址空间中分配特殊缓冲区。 而read函数必须执行的另一个操作是将信息复制到此缓冲区。 来自该空间的指针指向的地址和内核地址空间中的地址可以具有不同的值。 这就是我们不能简单地取消引用指针的原因。 使用这些指针时,我们有一组特定的宏和函数,我们在asm / uaccess.h文件中声明。 在我们的例子中,最合适的函数是copy_to_user() 。 它的名字不言而喻:它只是将特定数据从内核缓冲区复制到用户空间中分配的缓冲区。 此外,它还验证指针是否有效以及缓冲区大小是否足够大。 因此,可以相对容易地处理驱动器中的错误。 这是copy_to_user原型的代码:

    long copy_to_user( void __user *to, const void * from, unsigned long n );

    首先,该函数必须接收三个指针作为参数:指向缓冲区的指针,指向数据源的指针,以及指向复制的字节数的指针。 正如我们所提到的,错误返回的值不是零,并且在成功执行的情况下,该值将为零。 该函数包含_user宏,其任务是执行文档处理。 它有另一个有用的应用程序,它允许我们分析代码是否正确使用地址空间中的指针; 这是使用稀疏分析器完成的,稀疏分析器执行静态代码分析。 确保始终将用户地址空间指针标记为_user 。

    本教程仅包含没有实际设备的Linux驱动程序编程示例。 如果在读取设备文件后不需要返回除文本字符串以外的任何内容,那么这就足够了。

    这是实现read功能的代码:

    static const char    g_s_Hello_World_string[] = "Hello world from kernel mode!\n\0";
    static const ssize_t g_s_Hello_World_size = sizeof(g_s_Hello_World_string);
    static ssize_t device_file_read(
                            struct file *file_ptr
                           , char __user *user_buffer
                           , size_t count
                           , loff_t *position)
    {
        printk( KERN_NOTICE "Simple-driver: Device file is read at offset = %i, read bytes count = %u"
                    , (int)*position
                    , (unsigned int)count );
        /* If position is behind the end of a file we have nothing to read */
        if( *position >= g_s_Hello_World_size )
            return 0;
        /* If a user tries to read more than we have, read only as many bytes as we have */
        if( *position + count > g_s_Hello_World_size )
            count = g_s_Hello_World_size - *position;
        if( copy_to_user(user_buffer, g_s_Hello_World_string + *position, count) != 0 )
            return -EFAULT;    
        /* Move reading position */
        *position += count;
        return count;
    }

    构建内核模块的系统

    在我们为驱动程序编写代码之后,是时候构建它并查看它是否像我们期望的那样工作。 在早期的内核版本(例如2.4)中,构建模块需要来自开发人员的更多动作:编译环境需要单独准备,编译本身需要GCC编译器。 只有在那之后,开发人员才会收到一个* .o文件 - 一个可以加载到内核的模块。 幸运的是,这些时间早已过去,现在这个过程要简单得多。 今天,大部分工作都是由makefile完成的:它启动内核构建系统,并为内核提供有关构建模块所需组件的信息。 从单个源文件构建的模块需要makefile中的单个字符串。 创建此文件后,您只需要启动内核构建系统:

    obj-m := source_file_name.o

    如您所见,这里我们已将源文件名分配给模块,该文件将是* .ko文件。

    相应地,如果有多个源文件,则只需要两个字符串

    obj-m := module_name.o 
    module_name-objs := source_1.o source_2.o … source_n.o

    make命令初始化内核构建系统:

    要构建模块:

    make –C KERNEL_MODULE_BUILD_SYSTEM_FOLDER M=`pwd` modules

    要清理构建文件夹:

    make –C KERNEL_MODULES_BUILD_SYSTEM_FOLDER M=`pwd` clean

    模块构建系统通常位于 /lib/modules/uname -r/build中。 现在是时候准备模块构建系统了。 要构建第一个模块,请从构建系统所在的文件夹中执行以下命令:

    make modules_prepare

    最后,我们将我们学到的所有内容组合到一个makefile中:

    TARGET_MODULE:=simple-module
    # If we are running by kernel building system
    ifneq ($(KERNELRELEASE),)
        $(TARGET_MODULE)-objs := main.o device_file.o
        obj-m := $(TARGET_MODULE).o
    # If we running without kernel build system
    else
        BUILDSYSTEM_DIR:=/lib/modules/$(shell uname -r)/build
        PWD:=$(shell pwd)
    all : 
    # run kernel build system to make module
        $(MAKE) -C $(BUILDSYSTEM_DIR) M=$(PWD) modules
    clean:
    # run kernel build system to cleanup in current directory
        $(MAKE) -C $(BUILDSYSTEM_DIR) M=$(PWD) clean
    load:
        insmod ./$(TARGET_MODULE).ko
    unload:
        rmmod ./$(TARGET_MODULE).ko
    endif

    load目标加载构建模块, unload目标将其从内核中删除。

    在我们的教程中,我们使用了main.c和device_file.c中的代码来编译驱动程序。 生成的驱动程序名为simple-module.ko。

    加载和使用模块

    从源文件文件夹执行以下命令允许我们加载构建的模块:

    sudo make load

    执行此命令后,驱动程序的名称将添加到/proc/modules文件中,而模块注册的设备将添加到/proc/devices文件中。 添加的记录如下所示:

    Character devices: 1 mem 4 tty 4 ttyS … 239 Simple-driver …

    cat /proc/devices | grep  Simple-driver
    $ 239 Simple-driver

    前三个记录包含添加的设备的名称以及与之关联的主设备号。 次编号范围(0-255)允许在/ dev虚拟文件系统中创建设备文件。

    sudo mknod /dev/simple-driver c 239 0

    在我们创建设备文件之后,我们需要执行最终验证以确保我们所做的工作按预期工作。 要验证,我们可以使用cat命令显示内容:

    cat /dev/simple-driver
    $ Hello world from kernel mode!

    参考资料

    1. Linux设备驱动程序, Jonathan Corbet , Alessandro Rubini和Greg Kroah-Hartman的第3版: http://lwn.net/Kernel/LDD3/
    2. Peter Jay Salzman和Ori Pomeranz撰写的Linux内核模块编程指南: http://tldp.org/LDP/lkmpg/2.6/html/lkmpg.html
    3. Linux Cross Reference http://lxr.free-electrons.com/ident

    代码下载

    Demo代码下载

    尾文

    device_file.c有一点编译问题,这样修正。

    copy_to_user -> raw_copy_to_user

    查看内核日志

    dmesg

    玩~

    展开全文
  • Linux设备驱动程序》的源码,Linux设备驱动程序这本书堪称经典,只有结合源码,才能真正学习该书的精华。另外还有Linux别的驱动解析
  • Linux设备驱动程序和设备文件

    千次阅读 2018-01-14 18:25:10
    Linux设备驱动程序和设备文件 设备驱动程序 一个设备驱动程序是一个管理着系统与某种特定硬件之间交互作用的程序。驱动程序在设备可理解的硬件指令和内核使用的固定编程接口之间起转换作用。驱动程序层的存在有...
  • Linux设备驱动程序 三 字符设备驱动 笔记 第三章 字符驱动设备 本章会编写一个完整的字符设备,字符设备简单,易于理解, 名字是scull:Simple Caracter Utility for Loading Localities,区域装载的简单字符...
  • 精通LINUX设备驱动程序开发 中文扫描

    千次下载 热门讨论 2012-09-05 16:08:21
    精通LINUX设备驱动程序开发 Linux 中文 扫描 高清
  • 精通LINUX设备驱动程序开发.pdf免费下载
  • LINUX设备驱动程序第三版.pdf免费下载链接(.pdf书籍的优点是便于直接在电脑中保存有电脑就可以阅读,如果觉得这本书给你提供到了很大的帮助,可以去书店补一本纸质版) 资源保存在腾讯微云上,下载不需要微云...
  • 第1章 Linux设备驱动程序模型 Linux系统中包含字符设备、块设备、网路设备三类基本的设备驱动程序。随着技术的不断进步,Linux驱动程序体系的拓扑结构越来越复杂,Linux 2.4内核已经不能适应这种形势的需求。...
  • Linux设备驱动程序学习笔记

    千次阅读 2017-05-15 18:02:51
    Linux设备驱动程序的作用 简介 设备 驱动程序就像一个个的“黑盒子”,使某个特定硬件响应一个定义良好的内部编程接口,这些操作完全隐藏了设备的工作细节。用户的操作通过一组标准化的调用执行,而这些调用独立...
  • linux设备驱动程序总述

    千次阅读 2015-01-25 10:39:04
    Linux设备驱动程序的作用 设备 驱动程序就像一个个的“黑盒子”,使某个特定硬件响应一个定义良好的内部编程接口,这些操作完全隐藏了设备的工作细节。用户的操作通过一组标准化的调用执行,而这些调用独立于特定的...
  • 一个块设备驱动程序主要通过传输固定大小的随机数据来访问设备。 Linux内核视块设备为字符设备相异的基本设备类型。 块驱动程序有自己完成特定任务的接口。...Linux设备驱动程序接口使得块设备可以发挥
  • 一、写linux设备驱动程序最难的地方就是没有强大的调试工具,一般间接或者直接的调试手段:  1. 利用printk  2. 查看OOP消息  3. 利用strace
  • Linux设备驱动程序学习之设备模型二

    千次阅读 2010-06-25 15:21:00
    Linux 设备驱动程序
  • 本篇分析Linux设备驱动程序第三版的第十六章,块设备驱动程序的代码,主要是其sbull模块在内核2.6.35.6-45版本下编译有错,这篇文章就是讲sbull模块迁移到了2.6.35.6-45这个模块下了,实现了正确编译,加载。...
  • linux设备驱动程序之简单字符设备驱动 一、linux系统将设备分为3类:字符设备、块设备、网络设备。使用驱动程序: 1、字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据...
  • LINUX设备驱动程序.第3版.完整版.pdf 个人收集电子书,仅用学习使用,不可用于商业用途,如有版权问题,请联系删除!
  • Linux设备驱动程序学习(16)-USB 驱动程序(一) 从此文档开始,内核使用2.6.29.4 很久没有写《LDD3》的学习笔记了,趁着做项目的机会,学习一下USB的驱动程序,并写学习笔记。 。 如果刚开始接触USB,会感觉...
  • 1.1 Linux设备驱动程序分类  Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加,主要是驱动程序的增加。在Linux内核的不断升级过程中,驱动程序的结构还是相对稳定。在2.0.xx到2.2....
  • 深入Linux设备驱动程序内核机制

    千次阅读 2012-02-21 20:24:16
    深入Linux设备驱动程序内核机制 陈学松 著 ISBN978-7-121-15052-4 2012年1月出版 定价:98.00元 16开 540页 内 容 简 介 这是一本系统阐述Linux设备驱动程序技术内幕的专业书籍,它的侧重点不是讨论如何在...
  • Linux设备驱动程序总述

    千次阅读 2012-06-20 15:33:47
    Linux设备驱动程序的作用 设备 驱动程序就像一个个的“黑盒子”,使某个特定硬件响应一个定义良好的内部编程接口,这些操作完全隐藏了设备的工作细节。用户的操作通过一组标准化的调用执行,而这些调用独立于特定的...
  • 这是《linux设备驱动程序》的全部源代码 学习linux设备驱动的时候可以参考一下
  • LINUX设备驱动程序第三版书中源码

    千次下载 热门讨论 2010-04-22 13:07:59
    linux device drivers sourcecode LINUX设备驱动程序第三版书中源码 书中的代码是在2.6.10版本下调试的。其它版本的内核在运行时要做修改。
  • 《深入Linux设备驱动程序内核机制》第2章 字符设备驱动程序 V1.2 (2012-03-19 20:14) 标签:  设备  blank  border  target  Linux 分类: Linux设备驱动 截止目前最新的更新... 第2章 ...
  • linux设备驱动程序简介

    千次阅读 2011-08-19 08:36:00
    linux设备驱动程序的作用 设备 驱动程序就像一个个的“黑盒子”,使某个特定硬件响应一个定义良好的内部编程接口,这些操作完全隐藏了设备的工作细节。用户的操作通过一组标准化的调用执行,而这些调用独立于特定的...
  • 什么是设备驱动程序同一个应用软件可以在不同的硬件平台的上运行。同样的open函数可以操作不同的硬件设备,实现设备无关性。这些功能的实现都离不开设备确定函数的支持。设备驱动程序是操作系统内核的内容。应用程序...

空空如也

1 2 3 4 5 ... 20
收藏数 15,497
精华内容 6,198
关键字:

linux设备驱动程序

linux 订阅