操作系统-课堂笔记-系统调用(南航)

文章目录

  • 系统调用
    • 系统调用功能概述
      • 1.什么是系统调用?
      • 2.系统调用提供哪些功能?
      • 3.提供系统调用有何优点?
    • 系统调用的处理过程
      • system_call()函数
      • sys_call_table
      • 在看看系统调用号
      • 系统调用和普通函数调用对比
      • 系统命令和内核函数
    • 系统调用的实例分析
      • 系统调用的参数传递
    • 如何增加一个系统调用
    • 小结

系统调用

系统调用功能概述

1.什么是系统调用?

  • OS内核中都有一组实现系统功能的过程,系统调用就是对上述过程的调用。
  • 编程人员利用系统调用,向OS提出服务请求,由OS代为完成。
  • 一般情况下,进程是不能够存取系统内核的,它不能存取内核使用的内存段,也不能调用内核函数,CPU的硬件结构保证了这一点,只是系统调用是个例外。

2.系统调用提供哪些功能?

系统调用是用户态进入内核态的唯一入口,常用的系统调用有:

  • write/read调用
  • 设置系统状态或读取内核数据:getpid(), getpriority(), setpriority(), sethostname()等
  • 进程管理相关:fork(), clone(), execve(), exit()等

3.提供系统调用有何优点?

系统调用其实和我们用的函数库一样,只不过这里的函数库很特殊,其涉及到硬件设备,所以系统调用的优点是:

  • 编程容易,从硬件设备的低级编程中解脱出来
  • 只有使用系统调用才能接触到内核,提高了系统的安全性,可以先检查请求的正确性,然后再执行

系统调用的处理过程

操作系统-课堂笔记-系统调用(南航)_第1张图片

  • Linux实现系统调用利用了i386体系结构中的软中断,即调用了int 0x80汇编指令。
  • 这条汇编指令产生向量为128的异常,CPU便切换到内核状态执行内核函数:system_call()。
  • int 0x80指令将用户态的执行模式转变成内核态,并将控制权交给系统调用过程的起点system_call()处理函数。

system_call()函数

  • system_call()函数首先检查系统调用号,该号码告诉内核进程请求哪种服务。
  • 内核进程查看系统调用表sys_call_table找到所调用的内核函数入口地址。
  • 接着调用相应的函数,在返回后做一些系统检查,最后返回到进程。

文字描述太无聊了,翻了翻Linux0.11版内核,看看它们怎么实现的叭:

  • 我发现system_call是用汇编实现的,对应system_call.s文件,大概就是下面这样:
    操作系统-课堂笔记-系统调用(南航)_第2张图片
    还有一个片段:
    操作系统-课堂笔记-系统调用(南航)_第3张图片
    表示Linux0.11版内核有72个系统调用!

system_call具体的执行过程见下图,上面的代码就不解析了,还是比较容易理解的!
操作系统-课堂笔记-系统调用(南航)_第4张图片操作系统-课堂笔记-系统调用(南航)_第5张图片
系统调用返回时:

  • 当服务例程结束时,system_call()从eax获得系统调用的返回值,并把这个返回值保存起来,然后跳转到ret_from_sys_call(),终止系统调用处理程序的执行。
  • 当进程恢复它在用户态的执行前,先恢复保存到堆栈的寄存器值,其中eax返回时会带回系统调用的返回码(负数说明调用错误,0或正数说明正常完成)。

sys_call_table

上面说过从sys_call_table里面找内核函数入口,那sys_call_table里面有什么东西呢?
操作系统-课堂笔记-系统调用(南航)_第6张图片
这里就简单看下sys_call_table长什么样子,想具体了解去读Linux内核源码哦~~!

上面还是有很多亲切的面孔的,比如exit, fork, write, read, open, dup2. getpid等等!

在看看系统调用号

操作系统-课堂笔记-系统调用(南航)_第7张图片
就是酱紫~!

系统调用和普通函数调用对比

  • API是用于某种特定目的的函数,供应用程序调用,而系统调用供应用程序直接进入系统内核。
  • Linux内核提供了一些C语言函数库,这些库对系统调用进行了一些包装和扩展,因为这些库函数与系统调用的关系非常紧密,所以习惯上把这些函数也称为系统调用。
  • 有的API函数在用户空间就可以完成工作,如一些用于数学计算的函数,因此不需要使用系统调用,有的API函数可能会进行多次系统调用。
  • 不同的API 函数也可能会有相同的系统调用。比如malloc(),calloc(),free()等函数都使用相同的方法分配和释放内存。

系统命令和内核函数

系统调用与系统命令

  • 系统命令相对API来说,更高一层。每个系统命令都是一个执行程序,如ls命令等。这些命令的实现调用了系统调用。

系统调用与内核函数

  • 系统调用是用户进入内核的接口层,它本身并非内核函数,但是它由内核函数实现。
  • 进入内核后,不同的系统调用会找到各自对应的内核函数,这些内核函数被称为系统调用的“服务例程”。如系统调用getpid实际调用的服务例程为sys_getpid(),或者说系统调用getpid()是服务例程sys_getpid()的封装例程。

系统调用的实例分析

实例代码:

#include 
#include 
#include 
#include 
int main(void) {
 long ID;
 ID = getpid();
 printf ("getpid()=%ld\n", ID);
 return(0);
}

执行过程:

  • 该程序调用封装例程getpid()。该封装例程将系统调用号_NR_getpid(第20个)压入EAX寄存器
  • CPU通过int $0x80 进入内核,找到system_call(),并调用它
    (以下进入内核态)
  • 在内核中首先执行system_call(),接着执行根据系统调用号在调用表中查找到的对应的系统调用服务例程sys_getpid()。
  • 执行sys_getpid()服务例程。
  • 执行完毕后,转入ret_from_sys_call()例程,系统调用返回到用户态。

系统调用的参数传递

  • 很多系统调用需要不止一个参数,普通C函数的参数传递是通过把参数值写入堆栈(用户态堆栈或内核态堆栈)来实现的。
  • 但因为系统调用是一种特殊函数,它由用户态进入了内核态,所以既不能使用用户态的堆栈也不能直接使用内核态堆栈。
  • 在int $0x80汇编指令之前,系统调用的参数被写入CPU的寄存器。
  • 然后,在进入内核态调用系统调用服务例程之前,内核再把存放在CPU寄存器中的参数拷贝到内核态堆栈中。毕竟服务例程是C函数,它还是要到堆栈中去寻找参数的

系统调用使用寄存器来传递参数,要传递的参数有:系统调用号、系统调用所需的参数。

用于传递参数的寄存器有:eax保存系统调用号和系统调用返回值

系统调用参数保存在:ebx, ecx, edx, esi, edi

进入内核态后,system_call将上述值压栈,使其保存在内核态堆栈中,这一点在上面的代码截图中能直观的看到。

用寄存器传递参数必须满足两个条件:

  • 每个参数的长度不能超过寄存器的长度
  • 参数的个数不能超过6个(包括eax中传递的系统调用号);否则,需要用一个单独的寄存器指向进程地址空间中这些参数值所在的一个内存区即可

返回值必须写到eax寄存器中

如何增加一个系统调用

功能要求

  • 首先,自定义一个系统调用mysyscall ,它的功能是使用户的uid等于0 。然后,编写一段测试程序进行调用,思考将用户的uid设置成0意味着什么?
  • 意味着获取了管理员权限!

执行步骤如下,注笔者看的是Linux0.11版内核源码, 老师的ppt中用的是v86的,有点小区别!

  • 添加系统调用号:在unistd.h文件中
  • 在系统调用表中添加相应的表项:include/linux/sys.h中添加
  • 实现系统调用服务例程:kernal/sys.c中添加
    	int sys_mysyscall(void)
    	{
    		current->uid = current->euid = current->suid = current->fsuid = 0;
    		return 0;
    	}  
    
  • 重新编译内核,启动新内核
  • 编写一段测试程序检验实验结果
    #include 
    _syscall0(int,mysyscall)/* 注意这里没有分号 */
    int main()
    { mysyscall();
    printf(“This is my uid: %d. \n”, getuid());
    }
    

这里笔者还没有亲手做实验,后面尝试自己做做!

小结

系统调用的过程:
1、程序调用库的封装函数
2、调用软中断 int $0x80 进入内核。
3、在内核中首先执行system_call()函数,接着根据系统调用号在系统调用表中查找到对应的系统调用服务例程
4、执行该服务例程
5、执行完毕后,转入ret_from_sys_call例程,从系统调用返回

尝试回答以下问题:
什么是系统调用?它与普通函数调用有何区别?
什么是系统调用号和系统调用表?
系统调用的参数如何传递到内核?

本系列博客目录
下一篇:strace的使用

你可能感兴趣的:(Linux,南航-操作系统-课堂笔记)