精华内容
下载资源
问答
  • 系统调用

    千次阅读 多人点赞 2019-03-02 17:47:48
    程序接口通常是由各种类型的系统调用所组成的,因而,也可以说,系统调用提供了用户程序和操作系统之间的接口,应用程序通过系统调用实现其与 OS 的通信,并可取得它的服务。 处理器(CPU)=运算器+控制器+...

    程序接口是 OS 专门为用户程序设置的,也是用户程序取得 OS 服务的唯一途径。程序接口通常是由各种类型的系统调用所组成的,因而,也可以说,系统调用提供了用户程序和操作系统之间的接口,应用程序通过系统调用实现其与 OS 的通信,并可取得它的服务

     

    处理器(CPU)=运算器+控制器+寄存器+高速缓存

     

    系统调用的基本概念


    通常,在 OS 的核心中都设置了一组用于实现各种系统功能的子程序(过程),并将它们提供给应用程序调用。

     


    系统态和用户态


    在计算机系统中,通常运行着两类程序:系统程序和应用程序,为了保证系统程序不被应用程序有意或无意地破坏,为计算机设置了两种状态:

    • 系统态(也称为管态或核心态),操作系统在系统态运行
    • 用户态(也称为目态),应用程序只能在用户态运行。

    在实际运行过程中,处理机会在系统态和用户态间切换。相应地,现代多数操作系统将 CPU 的指令集分为特权指令和非特权指令两类。


    1) 特权指令——在系统态时运行的指令

    • 对内存空间的访问范围基本不受限制,不仅能访问用户存储空间,也能访问系统存储空间,
    • 特权指令只允许操作系统使用,不允许应用程序使用,否则会引起系统混乱。

     

    2) 非特权指令——在用户态时运行的指令

    一般应用程序所使用的都是非特权指令,它只能完成一般性的操作和任务,不能对系统中的硬件和软件直接进行访问,其对内存的访问范围也局限于用户空间。

     

     

    系统调用

     

    如上所述,一方面由于系统提供了保护机制,防止应用程序直接调用操作系统的过程,从而避免了系统的不安全性。但另一方面,应用程序又必须取得操作系统所提供的服务,否则,应用程序几乎无法作任何有价值的事情,甚至无法运行。为此,在操作系统中提供了系统调用,使应用程序可以通过系统调用的方法,间接调用操作系统的相关过程,取得相应的服务

    当应用程序中需要操作系统提供服务时,如请求 I/O 资源或执行 I/O 操作,应用程序必须使用系统调用命令。由操作系统捕获到该命令后,便将 CPU 的状态从用户态转换到系统态,然后执行操作系统中相应的子程序(例程),完成所需的功能。执行完成后,系统又将CPU 状态从系统态转换到用户态,再继续执行应用程序。

     

    系统调用和一般调用的区别:

    (1) 运行在不同的系统状态——调用程序是运行在用户态,而被调用程序是运行在系统态。

    (2) 状态的转换通过软中断进入

    • 一般的过程调用并不涉及到系统状态的转换,可直接由调用过程转向被调用过程。
    • 系统调用不允许由调用过程直接转向被调用过程。

    通常都是通过软中断机制,先由用户态转换为系统态,经核心分析后,才能转向相应的系统调用处理子程序。

    (3) 返回问题。

    在采用了抢占式(剥夺)调度方式的系统中,在被调用过程执行完后,要对系统中所有要求运行的进程做优先权分析。当调用进程仍具有最高优先级时,才返回到调用进程继续执行;否则,将引起重新调度,以便让优先权最高的进程优先执行。此时,将把调用进程放入就绪队列。

    (4) 嵌套调用。

    像一般过程一样,系统调用也可以嵌套进行,即在一个被调用过程的执行期间,还可以利用系统调用命令去调用另一个系统调用。当然,每个系统对嵌套调用的深度都有一定的限制,例如最大深度为 6。


    中断机制


    系统调用是通过中断机制实现的,并且一个操作系统的所有系统调用都通过同一个中断入口来实现。对于拥有保护机制的操作系统来说,中断机制本身也是受保护的,

     

     


    系统调用的类型


    对于一般通用的 OS 而言,可将其所提供的系统调用分为:进程控制、文件操纵、通信管理和系统维护等几大类。

     


    进程控制类系统调用


    这类系统调用主要用于对进程的控制,如创建一个新的进程和终止一个进程的运行,获得和设置进程属性等。


    1) 创建和终止进程的系统调用
    在多道程序环境下,为使多道程序能并发执行,必须先利用创建进程的系统调用来为欲参加并发执行的各程序分别创建一个进程。当进程已经执行结束时、 或因发生异常情况而不能继续执行时,可利用终止进程的系统调用来结束该进程的运行。

     

    2) 获得和设置进程属性的系统调用
    当我们创建了一个(些)新进程后,为了能控制它(们)的运行,应当能了解、 确定和重新设置它(们)的属性。这些属性包括: 进程标识符、进程优先级、最大允许执行时间等。此时,我们可利用获得进程属性的系统调用,来了解某进程的属性,利用设置进程属性的系统调用,来确定和重新设置进程的属性。


    3) 等待某事件出现的系统调用
    进程在运行过程中,有时需要等待某事件(条件)出现后方可继续执行。例如,一进程在创建了一个(些)新进程后,需要等待它(们)运行结束后,才能继续执行,此时可利用等待子进程结束的系统调用进行等待;

     


    文件操纵类系统调用


    对文件进行操纵的系统调用数量较多,有创建文件、删除文件、打开文件、关闭文件、读文件、写文件、建立目录、移动文件的读/写指针、改变文件的属性等。


    1) 创建和删除文件
    当用户需要在系统中存放程序或数据时,可利用创建文件的系统调用 creat,由系统根据用户提供的文件名和存取方式来创建一个新文件;当用户已不再需要某文件时,可利用删除文件的系统调用 unlink 将指名文件删除。


    2) 打开和关闭文件
    用户在第一次访问某个文件之前,应先利用打开文件的系统调用 open,将指名文件打开,即系统将在用户(程序)与该文件之间建立一条快捷通路。在文件被打开后,系统将给用户返回一个该文件的句柄或描述符;当用户不再访问某文件时,又可利用关闭文件的系统调用 close,将此文件关闭,即断开该用户程序与该文件之间的快捷通路。


    3) 读和写文件
    用户可利用读系统调用 read,从已打开的文件中读出给定数目的字符,并送至指定的缓冲区中;同样,用户也可利用写系统调用 write,从指定的缓冲区中将给定数目的字符写入指定文件中。read 和 write 两个系统调用是文件操纵类系统调用中使用最频繁的。

     


    进程通信类系统调用


    在 OS 中经常采用两种进程通信方式,即消息传递方式和共享存储区方式。

    当系统中采用消息传递方式时

    1. 先打开一个连接(由源进程发出一条打开连接的系统调用 open connection,目标进程则应利用接受连接的系统调用 accept connection
    2. 可以利用发送消息的系统调用 send message 或者用接收消息的系统调用 receive message 来交换信息。
    3. 通信结束后,还须再利用关闭连接的系统调用 close connection 结束通信。

    用户在利用共享存储区进行通信

    1. 先利用建立共享存储区的系统调用来建立一个共享存储区
    2. 再利用建立连接的系统调用将该共享存储区连接到进程自身的虚地址空间上
    3. 然后便可利用读和写共享存储区的系统调用实现相互通信。

    除上述的三类外,常用的系统调用还包括设备管理类系统调用和信息维护类系统调用,

     

     


    系统调用的实现

     

    系统调用的实现与一般过程调用的实现相比,两者间有很大差异。对于系统调用,控制是由原来的用户态转换为系统态,这是借助于中断和陷入机制来完成的,在该机制中包括中断和陷入硬件机构中断与陷入处理程序两部分。当应用程序使用 OS 的系统调用时,产生一条相应的指令,CPU 在执行这条指令时发生中断,并将有关信号送给中断和陷入硬件机构,该机构收到信号后,启动相关的中断与陷入处理程序进行处理,实现该系统调用所需要的功能。

     

    中断和陷入硬件机构

    1) 中断和陷入的概念(面试考点——中断与异常的区别)

    中断是指 CPU 对系统发生某事件时的这样一种响应: CPU 暂停正在执行的程序,在保留现场后自动地转去执行该事件的中断处理程序;执行完后,再返回到原程序的断点处继续执行。

    下图 表示中断时 CPU 的活动轨迹。还可进一步把中断分为外中断内中断

    • 外中断——是指由于外部设备事件所引起的中断,如通常的磁盘中断、打印机中断等;
    • 内中断——是指由于 CPU 内部事件所引起的中断,如程序出错(非法指令、地址越界)。内中断(trap)也被译为“捕获”或“陷入”。

    通常,陷入是由于执行了现行指令所引起的;而中断则是由于系统中某事件引起的,该事件与现行指令无关。由于系统调用引起的中断属于内中断,因此把由于系统调用引起中断的指令称为陷入指令。

     

    2) 中断和陷入向量(百度面试考过中断向量)
     

    • 针对不同的设备编制不同的中断处理程序,并把该程序的入口地址放在某特定的内存单元中。
    • 不同的设备也对应着不同的处理机状态字PSW,且把它放在与中断处理程序入口指针相邻接的特定单元中。

    在进行中断处理时,只要有了这样两个字,便可转入相应设备的中断处理程序,重新装配处理机的状态字和优先级,进行对该设备的处理。因此,我们把这两个字称为中断向量。相应地,把存放这两个字的单元称为中断向量单元

    类似地,对于陷入,也有陷入向量,不同的系统调用对应不同的陷入向量,在进行陷入处理时,根据陷入指令中的陷入向量,转入实现相应的系统调用功能的子程序,即陷入处理程序。由所有的中断向量和陷入向量构成了中断和陷入向量表,如图所示。

     

     

     

     


    UNIX 系统调用

     

     UNIX 系统调用的类型


    进程控制


    该类系统调用包括:创建进程的系统调用 fork、终止进程的系统调用 exit、等待子进程结束的系统调用 wait 等十多条。


    (1) 创建进程(fork)。

    一个进程可以利用 fork 系统调用来创建一个新进程。新进程是作为调用者的子进程,它继承了其父进程的环境、 已打开的所有文件、根目录和当前目录等,即它继承了父进程几乎所有的属性,并具有与其父进程基本上相同的进程映像。


    (2) 终止进程(exit)。

    一个进程可以利用 exit 实现自我终止。通常,在父进程创建子进程时,便在子进程的末尾安排一条 exit 系统调用。这样,子进程在完成规定的任务后,便可进行自我终止。子进程终止后,留下一记账信息 status,其中包含了子进程运行时记录下来的各种统计信息。


    (3) 等待子进程结束(wait)。

    wait 用于将调用者进程自身挂起,直至它的某一子进程终止为止。这样,父进程可以利用 wait 使自身的执行与子进程的终止同步。


    (4) 执行一个文件(exec)。

    exec 可使调用者进程的进程映像(包括用户程序和数据等)被一个可执行的文件覆盖,此即改变调用者进程的进程映像。该系统调用是 UNIX 系统中最复杂的系统调用之一。


    (5) 获得进程 ID。

    UNIX 系统提供了一组用于获得进程标识符的系统调用,比如,可利用 getpid 系统调用来获得调用进程的标识符,利用 getpgrp 系统调用来获得调用进程的进程组 ID,以及利用 getppid 系统调用来获得调用进程的父进程 ID 等。


    (6) 获得用户 ID。

    UNIX 系统提供了一组用于获得用户 ID 的系统调用,如 getuid 可用于获得真正的用户 ID,geteuid 用于获得有效用户 ID,getgid 用于获得真正用户组 ID 等。


    (7) 进程暂停(pause)。

    可用此系统调用将调用进程挂起,直至它收到一个信号为止。

     

     

    文件操纵


    用于对文件进行操纵的系统调用是数量最多的一类系统调用,其中包括创建文件、打开文件、关闭文件、读文件及写文件等二十多条。


    (1) 创建文件(creat)。

    系统调用 creat 的功能是根据用户提供的文件名和许可权方式,来创建一个新文件或重写一个已存文件。如果系统中不存在指名文件,核心便以给定的文件名和许可权方式来创建一个新文件;如果系统中已有同名文件,核心便释放其已有的数据块。创建后的文件随即被打开,并返回其文件描述符 fd。若 creat 执行失败,便返回“-1”。


    (2) 打开文件(open)。

    open 的功能是把有关的文件属性从磁盘拷贝到内存中,以及在用户和指名文件之间建立一条快捷的通路,并给用户返回一个文件描述符 fd。文件被打开后,用户对文件的任何操作都只须使用 fd 而非路径名。

     

    (3) 关闭文件(close)。

    在 UNIX 系统中,由于允许一个文件被多个进程所共享,故只有在无其他任何进程需要此文件时,才能真正关闭该文件


    (4) 读和写文件 read 和 write。

    仅当用户利用 open 打开指定文件后,方可调用 read 或write 对文件执行读或写操作。两个系统调用都要求用户提供三个输入参数:

    • ① 文件描述符fd。
    • ② buf 缓冲区首址。对读而言,这是用户所要求的信息传送的目标地址;对写而言,这则是信息传送的源地址。
    • ③ 用户要求传送的字节数 n byte。

    系统调用 read 的功能是试图从 fd 所指示的文件中去读入 n byte 个字节的数据,并将它们送至由指针 buf 所指示的缓冲区中;系统调用 write 的功能是试图把 n byte 个字节数据,从指针 buf 所指示的缓冲区中写到由 fd 所指向的文件中。


    (5) 连接和去连接(link 和 unlink)。

    为了实现文件共享,必须记住所有共享该文件的用户数目。为此,在该文件的索引结点中设置了一个连接计数 link。每当有一用户要共享某文件时,须利用系统调用 link 来建立该用户(进程)与此文件之间的连接,并对 i.link 做加 1操作。当用户不再使用此文件时,应利用系统调用 unlink 去断开此连接,亦即做 i.link 的减1 操作。当 i.link 减 1 后结果为 0 时,表示已无用户需要此文件,此时才能将该文件从文件系统中删除。故在 UNIX 系统中并无一条删除文件的系统调用。

     

     

    进程间的通信


    为了实现进程间的通信,在 UNIX 系统中提供了一个用于进程间通信的软件包,简称IPC。它由消息机制、共享存储器机制和信号量机制三部分组成。在每一种通信机制中,都提供了相应的系统调用供用户程序进行进程间的同步与通信之用。

    (1) 消息机制。

    用户(进程)在利用消息机制进行通信时,必须先利用 msgget 系统调用来建立一个消息队列。若成功,便返回消息队列描述符 msgid,以后用户便可利用 msgid 去访问该消息队列。用户(进程)可利用发送消息的系统调用 msgsend 向用户指定的消息队列发送消息;利用 msgrcv 系统调用从指定的消息队列中接收指定类型的消息。

    (2) 共享存储器机制。

    当用户(进程)要利用共享存储器机制进行通信时,必须先利用shmget 系统调用来建立一个共享存储区,若成功,便返回该共享存储区描述符 shmid。以后,用户便可利用 shmid 去访问该共享存储区。进程在建立了共享存储区之后,还必须再利用shmat 将该共享存储区连接到本进程的虚地址空间上。以后,在进程之间便可利用该共享存储区进行通信。当进程不再需要该共享存储区时,可利用 shmdt 系统调用来拆除进程与共享存储区间的连接。

    (3) 信号量机制。

    在 UNIX 系统中所采用的信号量机制,允许将一组信号量形成一个信号量集,并对这组信号量施以原子操作

     

     

    信息维护

     

    在 UNIX 系统中,设置了许多条用于系统维护的系统调用。


    (1) 设置和获得时间。

    超级用户可利用设置时间的系统调用(stime),来设置系统的日期和时间。如果调用进程并非超级用户,则 stime 失败。一般用户可利用获得时间的系统调用time 来获得当前的日期和时间。

    (2) 获得进程和子进程时间(times)。

    利用该系统调用可获得进程及其子进程所使用的CPU 时间,其中包括调用进程在用户空间执行指令所花费的时间,系统为调用进程所花费的 CPU 时间、子进程在用户空间所用的 CPU 时间、系统为各子进程所花费的 CPU 时间等,并可将这些时间填写到一个指定的缓冲区。

    (3) 设置文件访问和修改时间(utime)。

    该系统调用用于设置指名文件被访问和修改的时间。如果该系统调用的参数 times 为 NULL 时,文件主和对该文件具有写权限的用户,可将对该文件的访问和修改时间设置为当前时间;如果 times 不为 NULL,则把 times 解释为指向 utim buf 结构的指针,此时,文件主和超级用户能将访问时间和修改时间置入 utim buf结构中。

    (4) 获得当前 UNIX 系统的名称(uname)。

    利用该系统调用可将有关 UNIX 系统的信息存储在 utsname 结构中。 这些信息包括 UNIX 系统名称的字符串、系统在网络中的名称、 硬件的标准名称等。

     

     

    展开全文
  • Linux如何动态添加新的系统调用

    千次阅读 2020-05-06 22:25:04
    先来个满满的回忆: ...2011年写这篇文章的时候,我的女儿小小还没有出生。 评价一下这篇文章,总体写得还不错,时间如白驹过隙...前段时间折腾Rootkit的时候,我有意避开类似HOOK劫持系统调用的话题,我主要是想来点新...

    来自 《Linux动态为内核添加新的系统调用》

    先来个满满的回忆:
    https://blog.csdn.net/dog250/article/details/6446192
    2011年写这篇文章的时候,我的女儿小小还没有出生。

    评价一下这篇文章,总体写得还不错,但排版不行。时间如白驹过隙,快十年过去了,今天我来旧事重提。


    添加新的系统调用 ,这是一个老掉牙的话题。前段时间折腾Rootkit的时候,我有意避开涉及HOOK劫持系统调用的话题,我主要是想来点新鲜的东西,毕竟关于劫持系统调用这种话题,网上的资料可谓汗牛充栋。

    本文的主题依然不是劫持系统调用,而是添加系统调用,并且是动态添加系统调用,即在不重新编译内核的前提下添加系统调用,毕竟如果可以重新编译内核的话,那实在是没有意思。

    但文中所述动态新增系统调用的方式依然是老掉牙的方式,甚至和2011年的文章有所雷同,但是 这篇文章介绍的方式足够清爽!

    我们从一个问题开始。我的问题是:

    • Linux系统中如何获取以及修改当前进程的名字??

    你去搜一下这个topic,一堆冗余繁杂的方案,大多数都是借助procfs来完成这个需求,但没有直接的让人感到清爽的方法,比如调用一个getname接口即可获取当前进程的名字,调用一个modname接口就能修改自己的名字,没有这样的方法。

    所以,干嘛不增加两个系统调用呢:

    • sys_getname: 获取当前进程名。
    • sys_setname: 修改当前进程名。

    总体上,这是一个 增加两个系统调用的问题。

    下面先演示动态增加一个系统调用的原理。还是使用2011年的老例子,这次我简单点,用systemtap脚本来实现。

    千万不要质疑systemtap的威力,它的guru模式其实就是一个普通的内核模块,只是让编程变得更简单,所以, 把systemtap当一种方言来看待,而不仅仅作为调试探测工具。 甚至纯guru模式的stap脚本根本没有用到int 3断点,它简直可以用于线上生产环境!

    演示增加系统调用的stap脚本如下:

    #!/usr/bin/stap -g
    // newsyscall.stap
    %{
    unsigned char *old_tbl;
    // 这里借用本module的地址,分配静态数组new_tbl作为新的系统调用表。
    // 注意:不能调用kmalloc,vmalloc分配,因为在x86_64平台它们的地址无法被内核rel32跳转过来!
    unsigned char new_tbl[8*500] = {0};
    unsigned long call_addr = 0;
    unsigned long nr_addr = 0;
    unsigned int off_old;
    unsigned short nr_old;
    
    // 使用内核现成的poke text接口,而不是自己去修改页表权限。
    // 当然,也可以修改CR0,不过这显然没有直接用text_poke清爽。
    // 这是可行的,不然呢?内核自己的ftrace或者live kpatch怎么办?!
    void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len);
    %}
    
    %{
    // 2011年文章里的例子,打印一句话而已,我修改了函数名字,称作“皮鞋”
    asmlinkage long sys_skinshoe(int i)
    {
    	printk("new call----:%d\n", i);
    	return 0;
    }
    %}
    
    function syscall_table_poke()
    %{
    	unsigned short nr_new = 0;
    	unsigned int off_new = 0;
    	unsigned char *syscall;
    	unsigned long new_addr;
    	int i;
    
    	new_addr = (unsigned long)sys_skinshoe;
    	syscall = (void *)kallsyms_lookup_name("system_call");
    	old_tbl = (void *)kallsyms_lookup_name("sys_call_table");
    	_text_poke_smp = (void *)kallsyms_lookup_name("text_poke_smp");
    
    	// 拷贝原始的系统调用表,3200个字节有点多了,但绝对不会少。
    	memcpy(&new_tbl[0], old_tbl, 3200);
    	// 获取新系统调用表的disp32偏移(x86_64带符号扩展)。
    	off_new = (unsigned int)((unsigned long)&new_tbl[0]);
    
    	// 在system_call函数的指令码里进行特征匹配,匹配cmp $0x143 %rax
    	for (i = 0; i < 0xff; i++) {
    		if (syscall[i] == 0x48 && syscall[i+1] == 0x3d) {
    			nr_addr = (unsigned long)&syscall[i+2];
    			break;
    		}
    	}
    	// 在system_call函数的指令码里进行特征匹配,匹配callq  *xxxxx(,%rax,8)
    	for (i = 0; i < 0xff; i++) {
    		if (syscall[i] == 0xff && syscall[i+1] == 0x14 && syscall[i+2] == 0xc5) {
    			call_addr = (unsigned long)&syscall[i+3];
    			break;
    		}
    	}
    	// 1. 增加一个系统调用数量
    	// 2. 使能新的系统调用表
    	off_old = *(unsigned int *)call_addr;
    	nr_old = *(unsigned short *)nr_addr;
    	// 设置新的系统调用入口函数
    	*(unsigned long *)&new_tbl[nr_old*8 + 8] = new_addr;
    	nr_new = nr_old + 1;
    	memcpy(&new_tbl[nr_new*8 + 8], &old_tbl[nr_old*8 + 8], 16);
    	// poke 代码
    	_text_poke_smp((void *)nr_addr, &nr_new, 2);
    	_text_poke_smp((void *)call_addr, &off_new, 4);
    %}
    
    function syscall_table_clean()
    %{
    	_text_poke_smp((void *)nr_addr, &nr_old, 2);
    	_text_poke_smp((void *)call_addr, &off_old, 4);
    %}
    
    probe begin
    {
    	syscall_table_poke();
    }
    
    probe end
    {
    	syscall_table_clean();
    }
    

    唯一需要解释的就是两处poke:

    1. 修改系统调用数量的限制。
    2. 修改系统调用表的位置。

    我们从system_call指令码中一看便知:

    crash> dis system_call
    0xffffffff81645110 <system_call>:       swapgs
    ...
    # 0x143需要修改为0x144
    0xffffffff81645173 <system_call_fastpath>:      cmp    $0x143,%rax
    0xffffffff81645179 <system_call_fastpath+6>:    ja     0xffffffff81645241 <badsys>
    0xffffffff8164517f <system_call_fastpath+12>:   mov    %r10,%rcx
    # -0x7e9b2c40需要被修正为新系统调用表的disp32偏移
    0xffffffff81645182 <system_call_fastpath+15>:   callq  *-0x7e9b2c40(,%rax,8)
    0xffffffff81645189 <system_call_fastpath+22>:   mov    %rax,0x20(%rsp)
    

    如果代码正常,那么直接执行上面的stap脚本的话,新的系统调用应该已经生成,它的系统调用号为324,也就是0x143+1。至于说为什么系统调用号必须是逐渐递增的,请看:

    callq  *-0x7e9b2c40(,%rax,8)
    

    上述代码的含义是:

    call index * 8 + disp32_offset 
    

    这意味着内核是按照数组下标的方式索引系统调用的,这要求它们必须连续存放。

    好了,回到现实,我们上面的行动是否成功了呢?事情到底是不是我们想象的那样的呢?我们写个测试case验证一下:

    // newcall.c
    int main(int argc, char *argv[])
    {
    	syscall(324, 1234);
    	perror("new system call");
    }
    

    执行之,看结果:

    [root@localhost test]# gcc newcall.c
    [root@localhost test]# ./a.out
    new system call: Success
    [root@localhost test]# dmesg
    [ 1547.387847] stap_6874ae02ddb22b6650aee5cd2e080b49_2209: systemtap: 3.3/0.176, base: ffffffffa03b6000, memory: 106data/24text/0ctx/2063net/9alloc kb, probes: 2
    [ 1549.119316] new call----:1234
    

    OK,成功!此时我们Ctrl-C掉我们的stap脚本,再次执行a.out:

    [root@localhost test]# ./a.out
    new system call: Function not implemented
    

    完全符合预期。


    OK,那么现在开始正事,即新增两个系统调用,sys_getname和sys_setname,分别为获取和设置当前进程的名字。

    来吧,让我们开始。

    其实 newsyscall.stap 已经足够了,稍微改一下即可,但是这里的 稍微改 体现了品质和优雅:

    • 改为oneshot模式,毕竟我不希望有个模块在系统里。

    oneshot模式需要动态分配内存,保证在stap模块退出后这块内存不会随着模块的卸载而自动释放。而这个,我已经玩腻了。

    直接上代码:

    #!/usr/bin/stap -g
    // poke.stp
    %{
    // 为了rel32偏移的可达性,借用模块映射空间的范围来分配内存。
    #define START   _AC(0xffffffffa0000000, UL)
    #define END     _AC(0xffffffffff000000, UL)
    
    // 保存原始的系统调用表。
    unsigned char *old_tbl;
    // 保存新的系统调用表。
    unsigned char *new_tbl;
    // call系统调用表的位置。
    unsigned long call_addr = 0;
    // 系统调用数量限制检查的位置。
    unsigned long nr_addr = 0;
    // 原始的系统调用表disp32偏移。
    unsigned int off_old;
    // 原始的系统调用数量。
    unsigned short nr_old;
    void * *(*___vmalloc_node_range)(unsigned long, unsigned long,
                unsigned long, unsigned long, gfp_t,
                pgprot_t, int, const void *);
    void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len);
    %}
    
    %{
    // 新系统调用的text被copy到了新的页面,因此最好不要调用内核函数。
    // 这是因为内核函数之间的互调使用的是rel32调用,这就需要校准偏移,太麻烦。
    // 记住:作为例子,不调用printk,也不调用memcpy/memset...如果想秀花活儿,自己去校准吧。
    // 详细的秀法,参见我前面关于rootkit的文章。
    long sys_setskinshoe(char *newname, unsigned int len)
    {
    	int i;
    
    	if (len > 16 - 1)
    		return -1;
    
    	for (i = 0; i < len; i++) {
    		current->comm[i] = newname[i];
    	}
    	current->comm[i] = 0;
    	return 0;
    }
    
    long sys_getskinshoe(char *name, unsigned int len)
    {
    	int i;
    
    	if (len > 16 - 1)
    		return -1;
    
    	for (i = 0; i < len; i++) {
    		name[i] = current->comm[i];
    	}
    	return 0;
    }
    
    unsigned char *stub_sys_skinshoe;
    %}
    
    function syscall_table_poke()
    %{
    	unsigned short nr_new = 0;
    	unsigned int off_new = 0;
    	unsigned char *syscall;
    	unsigned long new_addr;
    	int i;
    
    	syscall = (void *)kallsyms_lookup_name("system_call");
    	old_tbl = (void *)kallsyms_lookup_name("sys_call_table");
    	___vmalloc_node_range = (void *)kallsyms_lookup_name("__vmalloc_node_range");
    	_text_poke_smp = (void *)kallsyms_lookup_name("text_poke_smp");
    
    	new_tbl = (void *)___vmalloc_node_range(8*500, 1, START, END,
    								GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC,
    								-1, NULL/*__builtin_return_address(0)*/);
    	stub_sys_skinshoe = (void *)___vmalloc_node_range(0xff, 1, START, END,
    								GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC,
    								-1, NULL);
    	// 拷贝代码指令
    	memcpy(&stub_sys_skinshoe[0], sys_setskinshoe, 90);
    	memcpy(&stub_sys_skinshoe[96], sys_getskinshoe, 64);
    	// 拷贝系统调用表
    	memcpy(&new_tbl[0], old_tbl, 3200);
    	new_addr = (unsigned long)&stub_sys_skinshoe[0];
    
    	off_new = (unsigned int)((unsigned long)&new_tbl[0]);
    	// cmp指令匹配
    	for (i = 0; i < 0xff; i++) {
    		if (syscall[i] == 0x48 && syscall[i+1] == 0x3d) {
    			nr_addr = (unsigned long)&syscall[i+2];
    			break;
    		}
    	}
    	// call指令匹配
    	for (i = 0; i < 0xff; i++) {
    		if (syscall[i] == 0xff && syscall[i+1] == 0x14 && syscall[i+2] == 0xc5) {
    			call_addr = (unsigned long)&syscall[i+3];
    			break;
    		}
    	}
    
    	off_old = *(unsigned int *)call_addr;
    	nr_old = *(unsigned short *)nr_addr;
    	// 设置setskinshoe
    	*(unsigned long *)&new_tbl[nr_old*8 + 8] = new_addr;
    	new_addr = (unsigned long)&stub_sys_skinshoe[96];
    	// 设置getskinshoe
    	*(unsigned long *)&new_tbl[nr_old*8 + 8 + 8] = new_addr;
    	// 系统调用数量增加2个
    	nr_new = nr_old + 2;
    	// 后移tail stub
    	memcpy(&new_tbl[nr_new*8 + 8], &old_tbl[nr_old*8 + 8], 16);
    	_text_poke_smp((void *)nr_addr, &nr_new, 2);
    	_text_poke_smp((void *)call_addr, &off_new, 4);
    	// 至此,新的系统调用表已经生效,尽情修改吧!
    %}
    
    probe begin
    {
    	syscall_table_poke();
    	exit();
    }
    

    顺便,我把恢复原始系统调用表的操作脚本也附带上:

    #!/usr/bin/stap -g
    // revert.stp
    %{
    void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len);
    %}
    
    function syscall_table_revert()
    %{
    	unsigned int off_new, off_old;
    	unsigned char *syscall;
    	unsigned long nr_addr = 0, call_addr = 0, orig_addr, *new_tbl;
    	// 0x143这个还是记在脑子里吧.
    	unsigned short nr_calls = 0x0143, curr_calls;
    	int i;
    
    	syscall = (void *)kallsyms_lookup_name("system_call");
    	orig_addr = (unsigned long)kallsyms_lookup_name("sys_call_table");
    	_text_poke_smp = (void *)kallsyms_lookup_name("text_poke_smp");
    
    	for (i = 0; i < 0xff; i++) {
    		if (syscall[i] == 0x48 && syscall[i+1] == 0x3d) {
    			nr_addr = (unsigned long)&syscall[i+2];
    			break;
    		}
    	}
    	for (i = 0; i < 0xff; i++) {
    		if (syscall[i] == 0xff && syscall[i+1] == 0x14 && syscall[i+2] == 0xc5) {
    			call_addr = (unsigned long)&syscall[i+3];
    			break;
    		}
    	}
    	curr_calls = *(unsigned short *)nr_addr;
    	off_new = *(unsigned int *)call_addr;
    	off_old = (unsigned int)orig_addr;
    	// decode出自己的系统调用表的地址。
    	new_tbl = (unsigned long *)(0xffffffff00000000 | off_new);
    	_text_poke_smp((void *)nr_addr, &nr_calls, 2);
    	_text_poke_smp((void *)call_addr, &off_old, 4);
    
    	vfree((void *)new_tbl[nr_calls + 1]);
    	/*
    	// loop free
    	// 如果你增加的系统调用比较多,且分布在不同的malloc页面,那么就需要循环free
    	for (i = 0; i < curr_calls - nr_calls; i ++) {
    		vfree((void *)new_tbl[nr_calls + 1 + i]);
    	}
    	*/
    	// 释放自己的系统调用表
    	vfree((void *)new_tbl);
    %}
    
    probe begin
    {
    	syscall_table_revert();
    	exit();
    }
    

    来吧,开始我们的实验!

    我不懂编程,所以我只能写最简单的代码展示效果,下面的C代码直接调用新增的两个系统调用,首先它获得并打印自己的名字,然后把名字改掉,最后再次获取并打印自己的名字:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char *argv[])
    {
    	char name[16] = {0};
    	syscall(325, name, 12);
    	perror("-- get name before");
    	printf("my name is %s\n", name);
    	syscall(324, argv[1], strlen(argv[1]));
    	perror("-- Modify name");
    	syscall(325, name, 12);
    	perror("-- get name after");
    	printf("my name is %s\n", name);
    	return 0;
    }
    

    下面是实验结果:

    # 未poke时的结果
    [root@localhost test]# ./test_newcall skinshoe
    -- get name before: Function not implemented
    my name is
    -- Modify name: Function not implemented
    -- get name after: Function not implemented
    my name is
    [root@localhost test]#
    [root@localhost test]# ./poke.stp 
    [root@localhost test]#
    # poke之后的结果,此时lsmod,你将看不到任何和这个poke相关的内核模块,这就是oneshot的效果。
    [root@localhost test]# ./test_newcall skinshoe
    -- get name before: Success
    my name is test_newcall
    -- Modify name: Success
    -- get name after: Success
    my name is skinshoe
    [root@localhost test]#
    [root@localhost test]# ./revert.stp
    [root@localhost test]#
    # revert之后的结果
    [root@localhost test]# ./test_newcall skinshoe
    -- get name before: Function not implemented
    my name is
    -- Modify name: Function not implemented
    -- get name after: Function not implemented
    my name is
    [root@localhost test]#
    

    足够简单,足够直接,工人们和经理都可以上手一试。

    我们如果让新增的系统调用干点坏事,那再简单不过了,得手之后呢?如何防止被经理抓到呢?封堵模块加载的接口即可咯,反正不加载内核模块,谁也别想看到当前系统的内核被hack成了什么样子,哦,对了,把/dev/mem的mmap也堵死哦…

    …不过这是下面文章的主题了。

    好了,今天就先写到这儿吧。


    浙江温州皮鞋湿,下雨进水不会胖。

    展开全文
  • 系统调用的概念及原理

    千次阅读 多人点赞 2019-10-31 16:48:30
    系统调用与内核函数 内核函数与普通函数形式上没有什么区别,只不过前者在内核实现,因此要满足一些内核编程的要求。 系统调用是用户进程进入内核的接口层,它本身并非内核函数,但它是由内核函数实现的,进入内核...

    系统调用与内核函数

    内核函数与普通函数形式上没有什么区别,只不过前者在内核实现,因此要满足一些内核编程的要求。
    系统调用是用户进程进入内核的接口层,它本身并非内核函数,但它是由内核函数实现的,进入内核后,不同的系统调用会找到相应的内核函数,这些内核函数被称为系统调用的“服务例程”。
    比如系统调用getpid()实际调用的是服务例程sys_getpid(),也可以说,系统调用getpid()是服务例程sys_getpid()的“封装例程”。

    系统调用基本概念

    计算机的各种硬件资源是有限的,为了更好的管理这些资源,用户进程是不允许直接操作的,所有对这些资源的访问都必须由操作系统控制。为此操作系统为用户态运行的进程与硬件设备之间进行交互提供了一组接口,这组接口就是所谓的系统调用。

    那么在应用程序和硬件之间设置这样一个接口层有什么优点呢?
    1. 把用户从学习硬件设备的低级编程特性中解放出来。
    2. 提高了系统的安全性,内核在满足某个请求之前就可以在接口级检查这个请求的正确性。
    3. 最重要的是,这些接口使得程序更具有可移植性,因为只要不同操作系统所提供的一组接口相同,那么在这些操作
    系统上就可以正确的编译和执行相同的程序。
    

    系统调用实质上就是函数调用,只不过调用的是系统函数,处于内核态而已。 用户在调用系统调用时会向内核传递一个系统调用号,然后系统调用处理程序通过此号从系统调用表中找到相应的内核函数执行,最后返回。

    系统调用号

    Linux系统有几百个系统调用,为了唯一的标识每一个系统调用,Linux为每一个系统调用定义了一个唯一的编号,这个编号就是系统调用号。(在我的电脑上,它是在 /usr/include/x86_64-linux-gnu/asm/unistd_32.h, 可以通过 find / -name unistd_32.h -print 查找)。
    在这里插入图片描述
    系统调用号的另一个目的是作为系统调用表的下标,当用户空间的进程执行一个系统调用的时侯,这个系统调用号就被用来指明到底是要执行那个系统调用。

    系统调用表

    为了把系统调用号与相应的服务例程关联起来,内核利用了一个系统调用表,存放在sys_call_table数组中,它是一个函数指针数组,每一个函数指针都指向其系统调用的封装例程,有NR_syscalls个表项,第n个表项包含系统调用号为n的服务例程的地址。

    系统调用处理函数

    系统调用最终还是会由内核函数完成,那么为什么不直接调用内核函数呢?
    这是因为用户空间的程序无法直接执行内核代码,因为内核驻留在受保护的地址空间上,不允许用户进程在内核地址空间上读写。所以,应用程序会以某种方式通知系统,告诉内核需要执行一个函数调用,这种通知机制是靠软中断来实现的,通过引发一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序就是所谓的系统调用处理程序。

    系统调用是属于操作系统内核的一部分,必须以某种方式提供给进程让它们去调用。CPU可以在不同的特权级别下运行,
    而相应的操作系统也有不同的运行级别,用户态和内核态。运行在内核态的进程可以毫无限制的访问各种资源,
    而在用户态下的用户进程的各种操作都有着限制,比如不能随意的访问内存、不能开闭中断以及切换运行的特权级别。
    所以操作系统通过中断从用户态切换到内核态。
    

    补充——中断是什么

    中断控制是计算机发展中一种重要的技术。 最初它是为克服对I/O接口控制采用程序查询所带来的处理器效率低而产生的。
    中断控制的主要优点是只有在I/O需要服务时才能得到处理器的响应,而不需要处理器不断地进行查询。
    由此,最初的中断全部是对外部设备而言的,称为外部中断(或硬件中断)。
    但随着计算机系统结构的不断改进以及应用技术的日益提高,中断的适用范围也随之扩大,出现了所谓的内部中断(或叫异常),
    它是为解决机器运行时所出现的某些随机事件及编程方便而出现的。因而形成了一个完整的中断系统。
    

    中断可分为两大类:异常中断
    异常分为故障陷阱特点是既不使用中断控制器,也不能被屏蔽(异常实际上是CPU发出的中断信号)。
    中断分为外部可屏蔽中断和外部非屏蔽中断,所有I/O设备产生的中断请求均引起屏蔽中断,而紧急事件(如硬件故障)引起的故障产生非屏蔽中断。

    Intel x86系列微机共支持256中向量中断,Linux对256个向量的分配如下:
    (1) 从0~31的向量对应异常和非屏蔽中断。
    (2) 从32~47的向量分配给屏蔽中断。
    (3) 从48~255的向量用来标识软中断。Linux只用了其中一个(128向量即0x80向量)用来实现系统调用。
    注:Linux为什么只使用一个中断号来对应所有的系统调用,而不是一个中断号对应一个系统调用?
    因为中断号是有限的,而系统调用又太多了。

    Linux对系统调用的调用必须通过执行int $0x80汇编指令,这条指令会产生向量为128的编程异常。
    

    中断有两个重要的属性,中断号中断处理程序。中断号用来标识不同的中断,不同的中断具有不同的中断处理程序。在操作系统内核中维护着一个中断向量表,这个数组存储了所有中断处理程序的地址,而中断号就是相应中断在中断向量表中的偏移量。

    在实地址模式中,CPU把内存中从0开始的1KB用来存储中断向量表。表中的每个表项占4个字节,
    由两个字节的段地址和两个字节的偏移量组成,这样构成的地址便是相应的中断处理程序的入口地址。
    但是,在保护模式下,由4字节的表项构成的中断向量表显然满足不了要求。这是因为:
    <1>除了两个字节的段描述符,偏移量必须用4字节来表示;
    <2>要有反映模式切换的信息。
    因此,在保护模式下,中断向量表中的表项由8个字节组成,中断向量表也改叫做中断描述符表
    IDT(Interrupt Descriptor Table)。其中的每个表项叫做一个门描述符(Gate Descriptor),
    “门”的含义是当中断发生时必须先通过这些门,然后才能进人相应的处理程序。
    

    Linux下系统调用原理

    根据上文,我们知道Linux使用一个中断号来对应所有的系统调用,那么只有一个中断号,操作系统怎么知道是哪一个系统调用要被调用呢?
    上文也提到过,系统调用拥有自己的系统调用号,系统调用表以及系统调用处理函数。一个调用号对应一个处理函数,通过处理函数和调用号在调用表中找到相应的系统调用。

    在Linux中,EAX寄存器是负责传递系统调用号的。
    以fork()为例,fork()的系统调用号为2,在执行int $0x80指令前,调用号会被存放在eax寄存器中,系统调用处理函数(中断处理函数)最终会通过系统调用号,调用正确的系统调用。

    伪代码:
    movl eax,2
    int 0x80
    

    在这里插入图片描述系统调用除了需要传递系统调用号以外,还是需要传递参数,并且具有返回值的,那么参数是怎么传递的呢?
    对于参数传递,Linux也是通过寄存器完成的。Linux最多允许向系统调用传递6个参数,
    分别依次由%ebx,%ecx,%edx,%esi,%edi和%ebp这个6个寄存器完成。

    以调用exit(1)为例

    伪代码:
    movl eax,2
    movl ebx,1
    int 0x80
    

    因为exit需要一个参数1,所以这里只需要使用ebx。这6个寄存器可能已经被使用,所以在传参前必须把当前寄存器的状态保存下来,待系统调用返回后再恢复。

    Linux中,在用户态和内核态运行的进程使用的栈是不同的,分别叫做用户栈和内核栈, 两者各自负责相应特权级别状态下的函数调用。当进行系统调用时,进程不仅要从用户态切换到内核态,同时也要完成栈切换, 这样处于内核态的系统调用才能在内核栈上完成调用。系统调用返回时,还要切换回用户栈,继续完成用户态下的函数调用。

    寄存器%esp(栈指针,指向栈顶)所在的内存空间叫做当前栈, 比如%esp在用户空间则当前栈就是用户栈,否则是内核栈。栈切换主要就是%esp在用户空间和内核空间间的来回赋值。 在Linux中,每个进程都有一个私有的内核栈,当从用户栈切换到内核栈时,需完成保存%esp以及相关寄存器的值(%ebx,%ecx…)并将%esp设置成内核栈的相应值。而从内核栈切换会用户栈时,需要恢复用户栈的%esp及相关寄存器的值以及保存内核栈的信息。

    之前总是听说系统调用很费时,在学习了系统调用的原理后,我们应该也知道了其中的原因

    1. 系统调用通过中断实现,需要从用户态切换到内核态,也就是要完成栈切换。
    2. 会使用寄存器传参,需要额外的保存和恢复的过程。

    以上关于系统调用的总结,都是本人从书本以及网上的资料,博客学习到的,如果由什么不对的地方,欢迎指正。

    展开全文
  • 浅谈C语言函数调用与系统调用

    千次阅读 2019-11-12 09:55:50
    1. 函数调用和系统调用概述 1.1 定性的去区分函数调用和系统调用 很多初学C语言的同学,亦或者开发中很少接触系统底层的同学可能会认为函数调用以及系统调用是一回事。因为在应用程序,两者都被抽象成接口去给应用...

    1. 函数调用和系统调用概述

    1.1 定性的去区分函数调用和系统调用

    很多初学C语言的同学,亦或者开发中很少接触系统底层的同学可能会认为函数调用以及系统调用是一回事。因为在应用程序,两者都被抽象成接口去给应用程序调用。其实函数调用和系统调用还是有区别,我们通过下图先有个全局的了解!

    图1-1 系统功能模块关联图

     

    从图1-1 我们可以知道应用程序访问内核,主要通过两种方式:中断和系统调用接口。

    其一,中断是置于程序流程之外的,所以这种方式并不是我们访问内核的普遍方式,更多的是因为程序异常而引起的中断;

    其二,就是系统调用接口,例如使用 open、read 以及 write 去操作文件数据(相当于去访问硬盘)。

    而函数调用是无法直接访问内核的,函数调用需要访问内核,也只能通过系统调用的方式。

    所以,我们可以通过这种方式定性地去区分函数调用和系统调用: 接口直接访问内核的属于系统调用,其他则为函数调用

    1.2 为什么要封装好系统调用

    我们知道,32位 linux系统为了保证程序独立性,给每一个进程分配独立的4G地址空间(虚拟内存空间),但是4G地址空间又被划分为两个空间:用户空间以及内核空间,如下图:

    图1-2 进程地址空间划分图

    用户空间,存储的是一些进程常见的数据,例如代码、变量数据、堆区和栈区等,需要注意的是内核空间,虽然说系统给每一个进程都分配1G大小的内核空间,但是实际上,内核空间是操作系统中进程公用的。公用就会带来一个现实性的问题,就比如我们可以在自己的房间干任何事情(用户空间),去不能在公共场合(内核空间)为所欲为,活动在公共场合(内核空间),我们的行为会受到限制,对于操作系统也一样道理,内核空间属于进程公用,进程去访问内核必须保证不会给其他进程造成影响。

    如何去保证进程操作系统中公用的内核空间不会出现非法操作,打个比方说:如何保证银行内部数据不被非法操作,银行是提供一个ATM给你,你只能选择上面的接口操作。操作系统也“学习”了这样的模式,封装接口,保证数据操作正确性!

    不仅仅应用程序无法直接访问内核空间,只能通过系统调用访问内核空间,而且内核对用户空间的数据的不信任,对于通过系统调用的传递过来的数据也会做相应的检查,确保内核安全,确保内核安全是必须的,内核属于系统的软件核心,内核崩溃会导致整个软件系统的崩溃。为了保证应用程序传递的数据合法性以及内核数据的绝密性,通常会使用copy_from_user去检验和拷贝传递过来的数据,使用copy_to_user()检验和拷贝数据给应用程序。

    2.函数调用

    2.1 什么叫做函数调用

    int add(int a, int b){
    
        return(a + b);
    }
    
    int main(){
    
        int sum;
        
        sum = add(1 + 2);
        
        return 0;
    }

    如上是一个最简单的函数调用过程,了解什么叫函数调用,我们可以通过了解什么是函数?

    函数(function)是完成特定任务的独立程序代码单元。

                                                                                                                                                         ----《C Primer Plus》

    再来了解为啥需要使用函数?

    首先,使用函数可以省去编写重复代码的苦差。如果程序多次完成某一项。如果程序需要多次完成某项任务,那么只需编写一个适合的函数,就可以在需要时使用这个函数,或者在不同的程序中使用该函数,就像许多程序使用putchar() 一样。

    其次,即使程序只完成某项任务一次,也值得使用函数。因为函数使程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。

                                                                                                                                                         ----《C Primer Plus》

    函数调用是很好理解的,提高代码重用性以及模块性,调用相对应函数,得到某种结果。

    2.1 函数调用的过程

    函数调用会发生函数压栈以及出栈过程,具体可以参考如下博文和视频(博主对汇编不熟悉,所以就不具体分析了,以免误人子弟!)。

    浅析函数的调用过程

    汇编中的函数调用中栈的工作过程

    3.系统调用

    3.1 什么叫做系统调用

    为了和用户空间上运行的进程进行交互,内核提供了一组接口。透过该接口,应用程序可以访问硬件设备和其他操作系统资源。这组接口在应用程序和内核之间扮演了使者的角色,应用程序发送各种请求,而内核负责满足这些请求(或者让应用程序暂时搁置)

                                                                                                                                             ----《Linux内核设计与实现》

    3.2 系统调用的过程

    系统调用和函数调用在应用程使用几乎没有差别,但是内部实现过程是完全不同的,函数调用是通过函数入口地址直接跳转函数服务程序,而系统调用并非直接跳转系统调用服务程序,因为内核地址对于应用程序是不可用的,需要通过中断的方式进入内核态,再通过一系列的操作找到对应的服务程序。

    我们可以通过下图有一个了解:

    图3-1 系统调用过程流程图

    希望更详细的了解系统调用过程可以看看下面这篇博文:

    ioctl系统调用过程(深入Linux(ARM)内核源码)

    展开全文
  • 系统调用原理及详细过程

    千次阅读 多人点赞 2020-07-09 21:24:23
    系统调用原理及详细过程 为什么要有系统调用? 由于系统的有限资源可能被多个不同的应用程序访问,因此,如果不加以保护,那么用程序难免产生冲突。所以,现代操作系统都将可能产生冲突的系统资源给保护起来,阻止...
  • Linux系统调用指南

    千次阅读 2019-01-13 17:45:35
    Linux系统调用指南 格式后头慢慢调,暂时先这样 原文链接: blog.packagecloud.io https://zcfy.cc/article/the-definitive-guide-to-linux-system-calls-670.html?t=new 这篇blog解释linux程序如何调用linux内核...
  • Linux系统调用

    千次阅读 2018-03-19 13:19:45
    1 概述相比Intel支持的快速系统调用指令sysenter/sysexit,AMD对应的是syscall/sysret,不过现在,Intel也兼容这两条指令。 测试环境:Ubuntu 12.04Ubuntu 16.04 642 传统系统调用int 0x80只用于32位系统,64位...
  • 操作系统作业:给linux系统增加一个系统调用

    万次阅读 多人点赞 2018-06-01 00:43:37
    前不久开了一门《操作系统》,老师上课留下了一个作业——给Linux系统添加一个系统调用。刚开始只能用一脸懵逼来形容,只能硬着头皮做下去。由于刚的新电脑,所以就没敢装双系统。所以我选择了虚拟机,虚拟机刚开始...
  • 什么是系统调用? Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。用户可以通过系统调用命令在自己的应用程序中调用它们。从某种角度来看,系统调用和普通的函数调用非常相似。区别仅仅在于,...
  • 系统调用(操作系统6)

    千次阅读 2020-10-03 16:27:24
    - 系统调用的作用 - 系统调用与库函数的区别 - 系统调用背后的过程
  • 在计算机中,系统调用(英语:system call),又称为系统呼叫,指运行在使用者空间的程序向 操作系统内核请求需要更高权限运行的服务。系统调用提供了用户程序与操作系统之间的接口( 即系统调用是用户程序和内核...
  • linux下的系统调用全过程

    万次阅读 2018-01-05 07:35:04
    系统调用是内核向用户进程提供服务的唯一方法,应用程序调用操作系统提供的功能模块(函数)。 用户程序通过系统调用从用户态(user mode)切换到核心态(kernel mode ),从而可以访问相应的资源。这样做的好处是...
  • C++ 函数调用和系统调用的区别

    千次阅读 2018-02-06 10:59:27
     所谓系统调用就是用户在程序中调用操作系统所提供的一个子功能,也就是系统API,系统调用可以被看做特殊的公共子程序。系统中的各种共享资源都由操作系统统一掌管,因此在用户程序中,凡是与资源有关的操作(如...
  • 本文将介绍Linux使用内核模块添加系统调用的方法(无需编译内核),思路就是修改映射在内存中的系统调用表,把一个空闲的系统调用表项指向自己写的模块中的函数,如果是已使用的表项,甚至可以实现系统调用劫持。...
  • 系统调用与普通过程调用的异同点

    千次阅读 2019-11-24 10:48:33
    系统调用与普通过程调用的异同点系统调用与普通过程调用的异同点 系统调用与普通过程调用的异同点 相同点: 改变指令流程 重复执行和公用 改变指令流程后需要返回原处 不同点: 系统调用是动态调用,而CALL调用方式...
  • 操作系统的系统调用

    千次阅读 2018-10-26 21:21:12
    系统调用:操作系统的接口是连接应用软件与操作系统的中间桥梁。接口在程序设计中表现的形式就是:函数。操作系统提供的函数就被称为系统调用(system call)。这里有个标准POSIX(Portable Operating System ...
  • 一、搞清楚系统调用这个实验叫我们干什么活(我差不多2个小时才搞清楚实验目的): 1.添加两个系统调用:sys_iam()和sys_whoami()。 2.自己写iam.c和whoami.c测试这两个系统调用。 3.在Bochs上用testlab2.sh来测试并...
  • TCP 系统调用

    千次阅读 2016-03-24 17:06:46
    典型的 TCP 客户机和服务器应用程序通过发布 TCP 系统调用序列来获取某些函数。这些系统调用包括socket ()、bind ()、listen ()、accept ()、send () 和 receive()。本文介绍在应用程序发布 TCP 系统...
  • 如何给Linux kernel 5添加一个系统调用

    千次阅读 2019-10-03 10:31:39
    如何给Linux Kernel 5添加一个系统调用 本篇博客从英文博客 Adding a Hello World System Call to Linux Kernel 全部翻译而来。原文链接为: ...
  • 4操作系统的系统调用

    千次阅读 2019-06-09 21:12:12
    操作系统的系统调用 目录 一、什么是系统调用系统调用的作用 二、系统调用和库函数的区别 三、系统调用背后的过程 一、什么是系统调用系统调用的作用 1、 操作系统作为用户和硬件的接口,向上提供一些简单易用的...
  • Linux-0.11 实验三 系统调用 实验报告

    千次阅读 2019-11-04 14:26:16
    能添加自定义系统调用,完成系统调用的全面控制; 为后续实验做准备。 二、实验内容和结果 1. 添加系统调用   首先在 kernel/下创建 who.c,实现两个系统调用的处理函数: #include <linux/kernel.h> #...
  • 操作系统之系统调用

    千次阅读 多人点赞 2020-02-08 17:21:23
    1.什么是系统调用 应用程序通过系统调用请求操作系统服务 应用程序不能直接申请系统资源,必须通过系统调用的方式向操作系统提出服务请求,由操作系统代为完成。这样保证了系统的稳定性和安全性。 系统调用分为 设备...
  • linux系统调用原理及实现

    千次阅读 2018-09-18 10:08:29
    linux系统调用 系统调用是linux内核为用户态程序提供的主要功能接口。通过系统调用,用户态进程能够临时切换到内核态,使用内核态才能访问的硬件和资源完成特定功能。系统调用由linux内核和内核模块实现,内核在...
  • 系统调用接口

    千次阅读 2018-11-27 09:53:59
    1、系统调用 操作系统作为系统软件,它的任务是为用户的应用程序提供良好的运行环境。因此,由操作系统内核提供一系列内核函数,通过一组称为系统调用的接口提供给用户使用。系统调用的作用是把应用程序的请求传递...
  • Linux添加系统调用

    千次阅读 2019-05-09 21:47:35
    修改内核代码添加函数,添加函数声明以及添加系统调用id,来实现给自己编译的内核添加系统调用。当然这个过程是在编译内核之前完成的,内核编译过程请参照Linux内核编译 添加系统调用 进入解压的文件目录 cd /usr/...
  • 关于Linux系统调用,内核函数【转】

    千次阅读 2018-12-24 12:13:38
    早上听人说到某个程序的一部分是...现在自己的理解是,用户程序不可用直接调用内核函数,除非通过系统调用接口。如果想调用哪个内核函数(或自己写的内核函数),怎么办?增加一个系统调用就行了。 原文如下: Linux...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,367,784
精华内容 1,347,113
关键字:

系统调用