zoukankan      html  css  js  c++  java
  • 20135220谈愈敏Linux Book_5

    第五章 系统调用

    • 内核提供了用户进程与内核进行交互的一组接口。
    • 应用程序发出请求->内核负责满足
    • 目的:保证系统稳定可靠

    5.1 与内核通信

    系统调用在用户空间进程和硬件设备之间添加了一个中间层,其作用:

    • 为用户空间提供一种硬件的抽象接口
    • 保证系统稳定和安全
    • 为了实现多任务和虚拟内存

    系统调用是用户空间访问内核的唯一手段

    5.2 API、POSIX和C库

    应用程序通过应用编程接口API而不是系统调用来编程,且API并不和系统调用一一对应。

    API、POSIX、C库和系统调用之间的关系:

    • POSIX是标准,API基于POSIX,POSIX定义的API函数和系统调用有直接关系。
    • 系统调用作为C库的一部分提供。C库实现看POSIX的主要API。
    • 程序员只和API打交道,内核只和系统调用打交道。

    提供机制而不是策略:系统调用抽象出用于完成某种确定目的的函数,而内核不关心函数怎么用。

    5.3 系统调用

    系统调用(syscall)通常通过C库中的函数调用来进行。

    系统调用有一个long类型返回值:通常负值表明错误,0表明成功。
    
    出现错误时C库把错误码写入errno全局变量,通过perror()库函数翻译成错误字符串。
    

    举例:getpid()

    注意:没有规定过程是什么方式,只要结果正确就行。

    SYSCALL_DEFINEO(getpid) //SYSCALL_DEFINEO是一个宏,定义一个无参数的系统调用,如下
    
    asmlinkage long sys_getpid(void)
    #asmlinkage限定词是编译指令,通知编译器仅从栈中提取该函数的参数,所有系统调用都要用到这个限定词。
    #函数返回long。用户空间为int,内核空间为long。
    #命名规则:getpid->sys_getpid
    

    系统调用号

    系统调用被赋予一个独一无二的系统调用号,进程通过系统调用号指明执行哪一个系统调用。

    系统调用号很重要:一旦分配不能有任何更改。系统调用被删除,系统调用号不允许被回收利用。

    sys_ni_syscall():未实现的系统调用,只返回-ENOSYS,专门针对无效的系统调用,负责“填补空缺”。
    
    sys_call_table:记录已注册的系统调用,为每一个有效的系统调用指定唯一的系统调用号。
    

    系统调用的性能

    Linux系统调用执行快:

    • 上下文切换时间短,进出内核简洁高效。
    • 系统调用处理程序和系统调用本身都非常简洁。

    5.4 系统调用处理程序

    用户空间的程序不能直接执行内核代码,需要通知系统切换到内核态,由内核执行系统调用。

    机制:软中断:引发一个异常使系统切换到内核态执行异常处理程序,而这个异常处理程序就是系统调用处理程序(system_call),中断号为128,通过int $0x80触发该中断。

    指定恰当的系统调用

    把系统调用号传给内核:通过eax寄存器

    检查系统调用号有效性:与NR_syscalls做比较:

    大于或等于,函数就返回-ENOSYS
    
    否则就执行相应的系统调用:call *sys_call_table(,%rax,8) //表项为8字节存储 
    

    参数传递

    外部参数输入:可存放在寄存器里,ebx,ecx,edx,esi,edi按顺序存放前五个参数,需要6个参数时:用寄存器存放指向这些参数的指针。

    返回值:存放在eax中。

    5.5系统调用的实现

    给内核添加一个新的系统调用是相对容易的,但是设计和实现一个系统调用是难题。

    实现系统调用

    • 第一步决定用途,要明确。
    • 新系统调用的参数、返回值、错误码、接口力求简洁,语义和行为稳定不做改动。
    • 为将来多做考虑,注意可移植性和健壮性。

    参数验证

    系统调用必须检查参数是否合法有效,进程不能让内核去访问它本身无权访问的资源。

    重要检查:指针是否有效:

    • 指针指向的内存区域属于用户空间,且在进程地址空间里。
    • 标记为可读/写/执行,才能进行读/写/执行。

    内核空间和数据空间之间的数据拷贝:

    写入数据:copy_to_user()
    读取数据:copy_from_user()
    //都有三个参数,进程空间中的目的内存地址+内核空间内的源地址+数据长度(字节数)
    //执行失败:返回没有拷贝的数据字节数  成功:返回0  错误:返回标准-EFAULT
    

    是否有合法权限:

    使用capable()函数来检查是否有权对指定资源进行操作:返回非0:有权,返回0:无权。

    5.6 系统调用上下文

    内核在执行系统调用时处于进程上下文。current指针指向引发系统调用的进程。

    在进程上下文中:

    • 内核可以休眠
    • 内核可以被抢占:保证系统调用是可重入的

    系统调用返回时:system_call负责切换到用户空间。

    绑定一个系统调用的最后步骤

    编写完一个系统调用后,注册步骤:

    • 在系统调用表的最后加入一个表项。从0算起,在该表位置为其系统调用号。
    • 对于所支持的所有体系结构,系统调用号都必须定义在<asm/unistd.h>中。每种体系结构不需要对应相同的系统调用号。系统调用号专属于体系结构ABI。
    • 系统调用必须被编译进内核映象(不能被编译成模块)。可放在kernel/sys.c中(包含各种系统调用)

    从用户空间访问系统调用

    一般系统调用靠C库支持,Linux提供了一组宏可直接对系统调用进行访问,会将系统调用号和参数压入寄存器并触发软中断来陷入内核。

    这些宏是_syscalln():n为0到6,代表参数个数。举例open():

    定义:long open (const char *filename,int flags,int mode)
    
    宏:
    #define NR_open 5   //系统调用号
    _syscall3(long,open,const char *,filename,int,flags,int,mode)
    //第一个参数是返回值类型,第二个是系统调用名称,后面是按照参数顺序排列的每个参数的类型和名称
    

    为什么不通过系统调用的方式实现

    虽然新建一个系统调用很容易且使用方便,性能高,但是不提倡。

    替代方法:实现一个设备节点,并对此实现read()和write()。使用ioctl()对特定的设置进行操作或对特定的信息进行检索。

    • 信号量这样的接口可用文件描述符表示。
    • 把增加的信息作为一个文件放在sysfs的合适位置。
  • 相关阅读:
    将MySQL中的数据导入到Solr
    Solr中schema.xml的Field介绍
    Window下Nginx的安装和启动
    Idea中运行shell脚本
    window10 下安装Mysql5.6
    Solr Admin管理界面使用说明
    Solr 整合中文分词器mmseg4j
    Solr安装和使用
    ActiveMQ 安装和使用
    AbstractQueuedSynchronizer源码解析
  • 原文地址:https://www.cnblogs.com/tymjava/p/5313085.html
Copyright © 2011-2022 走看看