当前位置: 首页 > Linux

札记,Linux内核设计与实现,5

时间:2023-04-07 00:31:01 Linux

系统调用系统调用是用户进程与内核交互的一组接口。实现:应用程序对硬件设备(硬件抽象接口)的访问是有限的,访问判断是基于权限。能够创建进程间通信机制来申请操作系统的其他资源。系统调用是用户控件访问内核的唯一方式,异常和陷阱除外。API、POSIX和C库API:应用程序通过用户控件实现的应用程序编程接口。系统调用是通过API而不是直接调用,不需要和系统调用一一对应。POSIX的目标是提供一个基于Unix的可移植操作系统标准。POSIX定义的API函数和系统调用都有直接的关系。linux的系统调用可以作为C库的一部分提供。C库实现了Unix系统的主要API,并提供了大部分POSIXAPI。程序员只和API打交道,Unix的接口设计原则是只提供机制不提供策略。使用Linux系统调用程序员通过API调用进行系统调用,重点关注输入、输出和全局错误代码errno变量。通过perror()库函数打印errno变量。定义系统调用:asmlinkagelongsys_getpid(void)限定符asmlinkage是一条编译指令,通知编译器只从堆栈中提取函数的参数。所有系统调用都需要这个限定符。函数的返回值很长,兼顾32位和64位。系统调用在用户空间和内核空间有不同的返回值类型,在用户空间是int,在内核空间是long。系统调用号每个系统调用对应一个系统调用号。该过程没有提及系统调用的名称。一旦分配,就不能更改,否则系统会崩溃,不兼容。在系统被删除或放弃后,Linux有一个未实现的系统调用sys_ni_syscall(),它除了返回-ENOSYS之外什么都不做。sys_call_table系统调用表记录了所有已注册系统调用的列表。不同的架构有不同的系统调用表。arch/i386/kernel/syscall_64.c中的x86系统调用性能Linux系统调用性能优于其他操作系统,因为它具有更短的上下文切换,并且系统调用处理程序和每个系统调用本身都比较简洁。Systemcallhandler用户态进程通过软中断的方式通知内核需要执行系统调用:触发异常,提示系统切换到内核态执行异常处理程序,即系统调用handler,interruptnumber128,int$0x80指令触发中断,system_call(),与硬件架构密切相关,为x86-64编写的汇编entry_64.S增加了sysenter指令。落入内核时,需要将系统调用号传递给内核。在x86上,系统调用号通过寄存器eax.system_call()与NR_syscalls进行比较。如果大于等于NR_syscalls,函数返回-ENOSYS,否则执行对应的系统。调用:call*sys_call_table(,%rax,8)由于系统调用表中的条目是以64位(8字节)类型存储的,内核需要对给定的系统调用号进行*4,然后将结果进行转换查询这个表中的位置(????),在x86-32系统上,代码很相似,只是用4代替了8。系统调用的参数是通过寄存器传递的。ebx,ecx,edx,esi和edi存储前5个参数,6个或更多,并使用单独的寄存器存储指向这些参数在用户空间中的地址的指针。返回值也是通过寄存器传递的,在eax寄存器中。系统调用实现在linux中设计和实现一个系统调用是一个难题,加入内核并不麻烦。判断目的,调用参数,返回值,错误码。力求简单性、稳定性、向后兼容性和可移植性。许多系统调用通过提供标志参数来确保向前兼容性。标志不是用来使单个系统调用具有多种不同的行为,而是用来添加新的功能和选项而不破坏向后兼容性或需要额外的新系统调用。不要假设机器的字节长度和字节顺序。参数校验、用户输入、用户指针(访问权限问题,指向的区域必须是用户控件,指针指向的内存区域在进程的地址空间中,区分可读、可写、可执行权限)copy_from_user()、copy_to_user():目的地址、源地址、内存长度。可能造成堵塞。当包含用户数据的页面被交换到硬盘而不是物理内存时,进程会休眠,直到页面错误处理程序将页面从硬盘交换回物理内存。权限验证:通过capable()函数检查是否有权限对特定资源进行操作,不为0则有操作权限。系统调用上下文,如果执行系统调用,内核在进程上下文中,当前指针指向当前任务。在进程上下文中,内核可以休眠(系统调用阻塞或schedule()调用)可以被抢占,新进程可以使用同一个系统调用,所以要注意系统调用是否可重入。当系统调用返回时,控制权还在system_call(),最终会负责切换回用户空间,让用户进程继续执行。绑定系统调用在系统调用表entry.s中添加一个条目,从0端开始与架构强相关,系统调用必须编译进内核镜像,不能编译成模块,放在kernel/下一个相关文件就可以了,比如sys.cENTRY(sys_call_table).longsys_restart_syscall....没有明确的指向号,默认从0开始-需要增加systemcall系统调用号foreacharchitectureto,#define__NR_restart_systemcall0...从用户空间访问系统调用通过c库调用,新添加的系统调用可能不支持系统调用可以使用宏访问,他会设置寄存器并调用Trappedin指令。_syscalln()//0-6,代表传递给系统调用的参数个数longopen(constchar*filename,intflags,intmode)#defineNR_open5//在中定义,是systemCallnumber_syscall3(long,open,constchar,*filename,int,flags,int,mode)这个宏会展开成一个内联汇编的c函数。通过将系统调用编号和参数压入寄存器并触发软中断来陷入内核。只需将上述宏放入您的应用程序中即可。不建议实现系统调用。系统调用在linux中易于创建和使用,而且性能高。问题是需要一个系统调用号。官方配置在加入稳定内核后固化。它不能更改,需要在不同的架构中注册。在脚本中使用系统调用并不容易,也不可能改为从文件系统访问系统调用。该方法实现了一个设备节点,实现了读写,使用ioctl操作特定的设置或者获取特定的信息。一些像信号量这样的接口可以用文件描述符来表示,所以可以按照上面的方法对其进行操作,将添加的信息作为一个文件放在sysfs的合适位置。