在读者看本文之前,最好已经有部分裸机程序基础(如点亮LED、arm-linux-gcc、中断等),初学者可以查看:ARM裸机加强版
本文使用的平台为TINY4412
移植等过程课查看之前的随笔:TINY4412:移植uboot、内核和挂接网络文件系统
Linux将存储器和外设分为3个基础大类:
1. 字符设备。
2. 块设备。
3. 网络设备。
对于用户而言,它们都需要使用文件系统的操作接口open()、read()、write()和close()进行访问。
对于驱动而言,我们需要完成的是操作接口函数对应的函数。
那么这两个的对应和调用关系是什么样的呢?
首先,接口函数会通过SWI实现从用户模式变换到管理模式。
SWI指令格式如下:
cond:执行指令的条件
immed_24:24位整数,用于区分用户的不同操作,从而执行不同内核函数。
读者到这里可能会有一个疑问,为什么要通过SWI进入管理模式呢?
类比于Windows,有的时候我们会使用管理员身份运行程序,这样的程序就会有更高的权利。
操作系统之所以这么做,是为应用程序的运行创建良好的环境,保障每个程序都可以最大化利用硬件资源,防止非法程序破坏其它应用程序执行环境。
为达目的,操作系统会将硬件的操作权限交给内核程序来管理,用户程序不能随意使用硬件,使用硬件(对硬件寄存器进行读写)时要先向操作系统发出请求,操作系统内核帮助用户程序实现其操作,操作系统通过一组称为系统调用的(System Call)的接口呈现给用户,系统调用把应用程序的请求传给内核,调用相应的内核函数完成所需的处理,将处理结果返回给应用程序。
内核中的系统调用定义在arch/arm/kernel/calls.S
/* 0 */ CALL(sys_restart_syscall) CALL(sys_exit) CALL(sys_fork_wrapper) CALL(sys_read) CALL(sys_write) /* 5 */ CALL(sys_open) CALL(sys_close) ... /* 375 */ CALL(sys_setns) CALL(sys_process_vm_readv) CALL(sys_process_vm_writev)
其中CALL的定义如下,为代码段的偏移地址,可以看到sys_open()的偏移是5
#define CALL(x) .long x
那么内核是怎么知道调用哪个函数呢?
在EABI模式下,系统调用通过传入的寄存器r7的值来判断调用哪个函数。
mov r7, #num @ num对应系统调用的某个函数 swi 0x0 @ 设置多少号软中断
原来的系统调用方式是这样:
swi (#num | 0x900000) @ 0x900000是个magic值
这两个选择是由宏CONFIG_OABI_COMPAT(原来的调用方式)和CONFIG_AEABI(EABI)控制的
对于原来的调用方式,内核给出的处理是给它建立一个单独的system call table,叫sys_oabi_call_table,这样,兼容方式下就会有两个system call table,以原来的调用方式的系统调用会执行old_syscall_table表中的函数指针,EABI方式的系统调用会用sys_call_table中的函数指针。
总结起来就是,通过SWI指令和寄存器传入参数,以查表方式找到对应的系统调用函数。
下图为内核中设备驱动的层次关系:
之后,如果我们想要控制LED的亮灭,由于LED属于字符驱设备驱动,因此程序就会依次通过应用程序open() -> SWI -> 系统调用sys_open() -> VFS -> 驱动程序open() -> 硬件操作
原文:https://www.cnblogs.com/Lioker/p/10832846.html