GNU/Linux的朴素认识与入门驱动程序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/windsnow1/article/details/87871685

GNU/Linux内核驱动编程学习之路

摘要:因为本科毕业设计跟Linux环境下驱动程序开发有关,一开始什么也没接触,没有过这方面的开发经验。应该是从零开始,借阅了很多大咖的书籍,参考了很多大牛博主的博文,现在把自己的一些理解和一些实验写下来,供大家一起讨论,欢迎大家一起交流。

1_GNU/Linux的朴素认识与入门驱动程序

Linux操作系统是UNIX操作系统的一种克隆系统,是一种类UNIX系操作系统,诞生于1991年10月5日,起初的作者是Linus Torvalds。
发展和成长主要依赖的五个支柱:UNIX操作系统Minix操作系统GNU计划POSIX标准Internet

GNU/Linux的朴素认识
从严格意义上说,可将操作系统定义为一种软件,它控制计算机的硬件资源,提供程序运行环境。我们通常称这种软件为内核,因为它相对较小,而且位于环境的核心。
下面是UNIX系统的体系结构:

内核的接口被称为系统调用。公用函数库构建在系统调用的接口之上,应用函数既可使用公用函数库,也可使用系统调用。shell是一个特殊的应用程序,为运行其他应用程序提供了一个接口。

Linux内核组成
https://www.kernel.org/可以下载Linux内核源码

  1. Linux内核源代码的目录结构
    arch:包含和硬件体系相关的代码,每种平台占一个相应的目录,如i386、arm、arm64、powerpc、mips等。
    block:块设备驱动程序I/O调度。
    crypto:常用的加密和散列算法(如AES、SHA等),还有一些压缩和CRC校验算法。
    documentation:内核各部分的通用解释和注释。
    drivers:设备驱动程序,每个不同的驱动占用一个子目录,如char,block,net,mtd,i2c等。
    fs:所支持的各种文件系统,如EXT、FAT、NTFS、JJFFS2等。
    include:头文件,与系统相关的头文件放置在include/linux子目录下。
    init:内核初始化代码。著名的start_kernel()就位于init/main.c文件中。
    ipc:进程间通信的代码。
    kernel:内核最核心的部分,包括进程调度、定时器等,而和平台相关的一部分代码放在arch/***/kernel目录下。
    lib:库文件代码。
    mm:内存管理代码,和平台相关的一部分代码放在arch/***/mm下。
    net:网络相关代码,实现各种各样的网络协议。
    scripts:用于配置内核的脚本协议。
    security:主要是一个SELinux的模块。
    sound:ALSA、OSS音频设备的驱动核心代码和常用设备驱动。
    usr:实现用于打包和压缩的cpio等。
  2. Linux内核的组成部分
    进程调度 内存管理 虚拟文件系统 网络接口 进程间通信
  3. Linux内核的内核空间和用户空间
    现代CPU内部往往实现了不同的操作模式(级别),不同模式下有不同功能,高层程序不能访问低级功能,而必须以某种形式切换到低级模式。

Linux内核驱动编程
Linux设备驱动会以内核模块的形式出现,设备驱动也是在内核状态下运行。
首先我们需要知道应用程序、库、内核、驱动程序之间的关系。如下图:

  1. 应用程序调用函数库,通过对文件的操作完成一系列功能;
    函数库:部分函数无需内核的支持,由库函数内部通过代码实现,直接完成功能;部分函数涉及到硬件操作或内核的支持,由内核完成对应功能,我们称其为系统调用。
    应用程序:以文件形式访问各种硬件设备(linux特有的抽象方式,把所有的硬件访问抽象为对文件的读写、设置)。

  2. 内核处理系统调用
    根据设备文件类型、主设备号、从设备号(后面会讲解),调用设备驱动程序;

  3. 设备驱动直接与硬件通信

设备类型
所有的硬件设备大致分为三类:字符设备、块设备、网络设备。
字符设备:字符(char)设备是个能够像字节流(类似文件)一样被访问的设备。
块设备:一个块设备驱动程序主要通过传输固定大小的数据(一般为512或1K)来访问设备。
网络设备:通过访问网络接口的方法。

驱动程序和应用程序的区别
应用程序从main函数开始,驱动程序从模块初始化函数module_init()函数作为入口。

第一个驱动程序编程
首先直接贴代码
1.驱动文件hello.c

//头文件,在linux内核源码include/linux目录下都可以查询到
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>

//主设备号 宏定义
#define    HELLO_MAJOR     231
//设备名 宏定义
#define    DEVICE_NAME     "HelloModule"

//打开操作
static int hello_open(struct inode *inode, struct file *file){
    printk(KERN_EMERG "hello open.\n");
    return 0;
}

//写操作
static ssize_t hello_write(struct file *file, const char __user * buf, size_t count, loff_t *ppos){
    printk(KERN_EMERG "hello write.\n");
    return 0;
}

//file_operations结构体中的成员函数与内核虚拟文件系统的接口。
static struct file_operations hello_flops = {
    .owner  =   THIS_MODULE,
    .open   =   hello_open,     
    .write  =   hello_write,
};

//模块加载函数 __init声明
static int __init hello_init(void){
    int ret;
    //申请设备号
    ret = register_chrdev(HELLO_MAJOR,DEVICE_NAME, &hello_flops);
    if (ret < 0) {
      printk(KERN_EMERG DEVICE_NAME " can't register major number.\n");
      return ret;
    }
    printk(KERN_EMERG DEVICE_NAME " initialized.\n");
    return 0;
}

//模块卸载函数 __exit声明
static void __exit hello_exit(void){
    unregister_chrdev(HELLO_MAJOR, DEVICE_NAME);
    printk(KERN_EMERG DEVICE_NAME " removed.\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

补充一下设备驱动与文件系统的关系:

2.Makefile:用来编译驱动文件hello.c

ifneq ($(KERNELRELEASE),)
MODULE_NAME = hellomodule
$(MODULE_NAME)-objs := hello.o
obj-m := $(MODULE_NAME).o
else
KERNEL_DIR = /lib/modules/$(shell uname -r)/build
MODULEDIR := $(shell pwd)

.PHONY: modules
default: modules

modules:
    make -C $(KERNEL_DIR) M=$(MODULEDIR) modules

clean distclean:
    rm -f *.o *.mod.c .*.*.cmd *.ko
    rm -rf .tmp_versions
endif

3.测试程序hellotest.c

#include <fcntl.h>
#include <stdio.h>

int main(void)
{
    int fd;
    int val = 1;
    fd = open("/dev/hellodev", O_RDWR);
    if(fd < 0){
        printf("can't open!\n");
    }
    write(fd, &val, 4);
    return 0;
}

测试:
将这三分代码放置在一个目录下:

make

生成驱动模块 hello.ko

sudo insmod hellomodule.ko

加载驱动

使用dmesg命令查看:

dmesg | tail
sudo mknod /dev/hellodev c 231 0
sudo chmod 777 /dev/hellodev

创建设备节点文件并赋予权限:

gcc hellotest.c -o hellotest

编译测试程序

运行测试程序

./hellohost
sudo rmmod hellomodule
dmesg | tail

卸载驱动并查看信息

这是一个简单的demo,对于整个驱动开发流程有了一定的了解。麻雀虽小,五脏俱全。缺少的可能是对硬件的操作,还有中断,DMA等各种其他操作。参考了很多了大神大牛的博文,附上参考链接:
http://www.cnblogs.com/amanlikethis/p/4914510.html
https://www.cnblogs.com/mrzhangxinjie/p/7170736.html

参考书籍:
Linux设备驱动开发详解基于最新的Linux 4.0内核 宋宝华编著 机械工业出版社

欢迎大家指正错误,一起交流,为技术信仰而交流。谢谢!!!
之后我会继续编写驱动编写的文章,大家一起加油。

猜你喜欢

转载自blog.csdn.net/windsnow1/article/details/87871685