树莓派开发(6)——Linux内核结构框图及驱动开发

Linux内核结构框图及驱动开发

转载:https://blog.csdn.net/Zy_1213/article/details/126944356
作者:大头1213

一、Linux内核结构框图

在这里插入图片描述

  1. 函数库就像一个“黑匣子”,提供了一系列API支配内核运作,但你不知道内核发生了什么。
  2. 内核是一个很厉害的超级逻辑,把硬件底层的东西抽象化,对用户来说只需要调API就好了,根本不需要管寄存器,协议,总线…(单片机会去直接操作),这些全部由操作系统做好。动不动写个操作系统是不现实的。

二、图解linux系统架构

在这里插入图片描述

  1. 最内层是硬件,最外层是用户应用,比如浏览器等等。硬件是物质基础,而应用提供服务。
  2. 为了方便调用内核,Linux将内核的功能接口制作成系统调用(system call)。用户不需要了解内核的复杂结构,就可以使用内核。系统调用是操作系统的最小功能单位。一个操作系统,以及基于操作系统的应用,都不可能实现超越系统调用的功能。
  3. 系统调用提供的功能非常基础,所以使用起来很麻烦。一个简单的给变量分配内存空间的操作,就需要动用多个系统调用。Linux定义一些库函数(library routine)来将系统调用组合成某些常用的功能。上面的分配内存的操作,可以定义成一个库函数,比如常用的malloc。

三、驱动认知

  1. 为什么要学习写驱动
    1.1 原来树莓派开发使用厂家提供的wiringPi库,开发简单。
    1.2 但未来做开发时,不一定都是用树莓派,没有wiringPi库可以用。但只要能运行Linux,linux的标准C库一定有。
    1.3 学会根据标准C库编写驱动,只要能拿到linux内核源码,拿到芯片手册,电路图…就能做开发。
  2. 文件名与设备号
    2.1 linux一切皆为文件,其设备管理同样是和文件系统紧密结合。在目录/dev下都能看到鼠标,键盘,屏幕,串口等设备文件,硬件要有相对应的驱动,那么open怎样区分这些硬件呢?
    2.2 依靠文件名与设备号
    2.3 依靠文件名与设备号。在/dev下ls -l可以看到
    在这里插入图片描述
    2.4 设备号又分为:主设备号用于区别不同种类的设备;次设备号区别同种类型的多个设备。驱动插入到链表的位置(顺序)由设备号检索。
  3. 内核中存在一个驱动链表,管理所有设备的驱动。 驱动开发无非以下两件事:
    • 编写完驱动程序,加载到内核
    • 用户空间open后,调用驱动程序(驱动程序就是操作寄存器来驱动IO口,单片机51,32就是这种操作)

四、open函数打通上层到底层硬件的详细过程

  • 自我解析:
    内核中有个驱动链表,管理所有设备的驱动。当每个设备写好驱动程序后由设备号插入到链表,可通过设备号在连链表中检索。
    C语言上层调用open函数打开引脚驱动文件,系统会发生软中断,中断号为0x80,表示发生系统调用,接着调用sys_call,从用户态转变为内核态,接着调用虚拟文件系统的sys_open,通过open函数的设备名找到设备号,内核通过设备号在驱动链表中找到驱动文件。在驱动文件中对寄存器进行操作。
  • 用户空间调用open(比如open(“/dev/pin4”,O_RDWR))产生一个软中断(中断号是0x80),进入内核空间调用sys_call,这个sys_call在内核里面是汇编的,用Source Insight搜索不到。
  • sys_calll真正调用的是sys_open(属于VFS层虚拟文件系统,因为磁盘的分区和引脚分区不一样,为了实现上层统一化),根据你的设备名比如pin4去到内核的驱动链表,根据其主设备号与次设备号找到相关驱动函数。
  • 调用驱动函数里面的open,这个open就是对寄存器的操作,从而设置IO口引脚电平。这件事对于单片机来说特变容易,就两句话搞定:
    sbit pin4 = P1^4;
    pin4=1;
    
  • (对应下图的粉色笔迹)
    在这里插入图片描述

五、shell

  • shell(壳)是一个特殊的应用,也经常被称为命令行 。可以理解为是一个命令解释器

      例如:当我们输入“ls -l”的时候,它将此字符串解释为
      1.在默认路径找到该文件(/bin/ls),
      2.执行该文件,并附带参数"-l"。
    
  • 一个shell对应一个终端 (terminal)。曾经来说,终端是一个硬件设备,用来输入并显示输出。如今,由于图形化界面的普及,终端往往就像下图一样,是一个图形化的窗口。
    在这里插入图片描述

  • 你可以通过这个窗口输入或者输出文本,这个文本直接传递给shell进行分析解释,然后执行,本质就是提供和内核交互的程序。

六、驱动开发实操

  1. 将编写的驱动程序拷贝到Linux内核源码的/char目录下。
    /home/jiangyo/Desktop/lessPi/linux-rpi-4.14.y/drivers/char
  2. 配置当前目录下的Makfile,可参考hpet.o的编译。
    修正后,编译成模块:obj-m += pin4driver2.o
  3. 然后回到Linux内核的目录,编译内核,只生成modules。
    ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make -j4 modules
    
  4. 编译成功之后通过scp指令将ko文件拷贝到树莓派
    scp /home/jiangyo/Desktop/lessPi/linux-rpi-4.14.y/drivers/char/pin4driver2.ko pi@192.168.101.76:/home/pi/Desktop/
    scp ./pin4test pi@192.168.101.76:/home/pi/Desktop/
    
  5. pin4test.c通过交叉编译工具在虚拟机上编译完拷贝到树莓派。
    arm-linux-gnueabihf-gcc pin4test.c -o pin4test
    
  6. 加载内核驱动
    sudo insmod pin4driver2.ko
    可去/dev/路径下查看是否生成了模块名为pin4的设备文件,模块名pin4在驱动文件中定义。
    
    lsmod 指令查看当前加载的内核驱动模块
    sudo rmmod pin4driver2:删除当前驱动模块
    
  7. 给设备文件加可读可写的权限
    sudo chmod 666 /dev/pin4
    
  8. 运行应用测试文件pin4test。但是运行后并不会看到驱动文件中的打印信息,是因为需要通过dmesg命令查看内核态打印的数据

七、驱动源码

  1. 应用测试程序:
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
    	int fd;
    	int cmd, date = -1;
    	fd = open("/dev/pin4", O_RDWR);
    
    	if(fd == -1) {
    		perror("error:");
    	}
    	
    	printf("input 1:open, input 0:close\n");
    	scanf("%d",&cmd);
    	
    	if(cmd == 1) {
    		date = 1;
    	}
    	else if(cmd == 0) {
    		date = 0;
    	}
    	
    	write(fd, &date, sizeof(int));
    
    	return 0;
    }
    
  2. 驱动程序:
    #include <linux/fs.h>
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/device.h>
    #include <linux/uaccess.h>
    #include <linux/types.h>
    #include <asm/io.h>
    
    
    static struct class *pin4_class;
    static struct device *pin4_class_dev;
                     
    static dev_t devno;            //设备号
    static int major =231;       //主设备号
    static int minor =0;          //次设备号
    static char *module_name="pin4";   //模块名
    
    volatile unsigned int *GPFSEL_0 = NULL;
    volatile unsigned int *GPSET_0 = NULL;
    volatile unsigned int *GPCLR_0 = NULL;
    
    static int pin4_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
    {
            printk("pin4_read\n"); 
            return 0;
    }
    
    static int pin4_open(struct inode *inode,struct file *file)
    {
    
       printk("pin4_open\n");
       
       *GPFSEL_0 &= ~(0x6 << 12);
       *GPFSEL_0 |= (0x1 << 12);
    
       return 0;
    
    }
    
    
    static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
    {
    	int cmd;
       printk("pin4_write\n");
       
       copy_from_user(&cmd, buf, count);
    
    	printk("cmd = %d\n", cmd);
    
       if(cmd == 1) {
    		*GPCLR_0 &= ~(0x1 << 4);
    		*GPSET_0 |= (0x1 << 4);
       }
       else if(cmd == 0) {
    		*GPCLR_0 |= (0x1 << 4);
    		*GPSET_0 &= ~(0x1 << 4);
       }
    	else {
    		printk("unknown\n");
    	}
       return 0;
    }
    
    static struct file_operations pin4_fops = {
    
       .owner = THIS_MODULE,
       .open  = pin4_open,
       .write = pin4_write,
       .read  = pin4_read,
    
    
    
    };
    
    int __init pin4_drv_init(void)   // 真实驱动入口
    {
    
        int ret;
        devno = MKDEV(major,minor);   // 2.创建设备号 
        ret   = register_chrdev(major, module_name,&pin4_fops);    //3.注册驱动 告诉内核,把这个驱动加入到内核的链表中
    
        pin4_class=class_create(THIS_MODULE,"myfirstdemo");  // 让代码在dev自动生成设备
        pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件
    	
    	GPFSEL_0 = (volatile unsigned int *)ioremap(0x3f200000, 4);
    	GPSET_0  = (volatile unsigned int *)ioremap(0x3f20001C, 4);
    	GPCLR_0  = (volatile unsigned int *)ioremap(0x3f200028, 4);
    	
    
        return 0;
    }
    
    void __exit pin4_drv_exit(void)
    {
    	
    	iounmap(GPFSEL_0);
    	iounmap(GPSET_0);
    	iounmap(GPCLR_0);
    
     device_destroy(pin4_class,devno);
     class_destroy(pin4_class);
     unregister_chrdev(major, module_name);     //卸载驱动
    
    }
    
    module_init(pin4_drv_init);     //入口,  内核加载驱动的时候,这个宏会被调用
    module_exit(pin4_drv_exit);
    MODULE_LICENSE("GPL v2");
    
    
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值