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

    千次阅读 2013-11-16 23:37:41
    Linux系统调用接口、系统调用例程和内核服务例程之间的关系 向linux内核中添加三个系统调用(Ubuntu9.10) ++++++++++++++++++++++++++++++++++ 使用 Linux 系统调用的内核命令 作者:M. Tim Jones 转贴自:本...

    参考:

    使用 Linux 系统调用的内核命令
    linux系统调用
    深入理解Linux的系统调用
    Linux系统调用接口、系统调用例程和内核服务例程之间的关系
    向linux内核中添加三个系统调用(Ubuntu9.10)


    ++++++++++++++++++++++++++++++++++

    使用 Linux 系统调用的内核命令



    作者:M. Tim Jones 转贴自:本站原创

     

    Linux® 系统调用 —— 我们每天都在使用它们。不过您清楚系统调用是如何在用户空间和内核之间执行的吗?本文将探究 Linux 系统调用接口(SCI),学习如何添加新的系统调用(以及实现这种功能的其他方法),并介绍与 SCI 有关的一些工具。

    系统调用就是用户空间应用程序和内核提供的服务之间的一个接口。由于服务是在内核中提供的,因此无法执行直接调用;相反,您必须使用一个进程来跨越用户空间与内核之间的界限。在特定架构中实现此功能的方法会有所不同。因此,本文将着眼于最通用的架构 —— i386。

    在本文中,我将探究 Linux SCI,演示如何向 2.6.20 内核添加一个系统调用,然后从用户空间来使用这个函数。我们还将研究在进行系统调用开发时非常有用的一些函数,以及系统调用的其他选择。最后,我们将介绍 与系统调用有关的一些辅助机制,比如在某个进程中跟踪系统调用的使用情况。

    SCI

    Linux 中系统调用的实现会根据不同的架构而有所变化,而且即使在某种给定的体架构上也会不同。例如,早期的 x86 处理器使用了中断机制从用户空间迁移到内核空间中,不过新的 IA-32 处理器则提供了一些指令对这种转换进行优化(使用 sysenter 和 sy***it 指令)。由于存在大量的方法,最终结果也非常复杂,因此本文将着重于接口细节的表层讨论上。更详尽的内容请参看本文最后的 参考资料。

    要对 Linux 的 SCI 进行改进,您不需要完全理解 SCI 的内部原理,因此我将使用一个简单的系统调用进程(请参看图 1)。每个系统调用都是通过一个单一的入口点多路传入内核。eax 寄存器用来标识应当调用的某个系统调用,这在 C 库中做了指定(来自用户空间应用程序的每个调用)。当加载了系统的 C 库调用索引和参数时,就会调用一个软件中断(0x80 中断),它将执行 system_call 函数(通过中断处理程序),这个函数会按照 eax 内容中的标识处理所有的系统调用。在经过几个简单测试之后,使用 system_call_table 和 eax 中包含的索引来执行真正的系统调用了。从系统调用中返回后,最终执行 syscall_exit,并调用 resume_userspace 返回用户空间。然后继续在 C 库中执行,它将返回到用户应用程序中。


    图 1. 使用中断方法的系统调用的简化流程
    系统调用的简化流程

    SCI 的核心是系统调用多路分解表。这个表如图 2 所示,使用 eax 中提供的索引来确定要调用该表中的哪个系统调用(sys_call_table)。图中还给出了表内容的一些样例,以及这些内容的位置。(有关多路分解的更多内容,请参看侧栏 “系统调用多路分解”)


    图 2. 系统调用表和各种链接
    系统调用表和各种链接

    添加一个 Linux 系统调用

    系统调用多路分解

    有些系统调用会由内核进一步进行多路分解。例如,BSD(Berkeley Software Distribution)socket 调用(socket、bind、 connect 等)都与一个单独的系统调用索引(__NR_socketcall)关联在一起,不过在内核中会进行多路分解,通过另外一个参数进入适当的调用。请参看 ./linux/net/socket.c 中的 sys_socketcall 函数。

    添加一个新系统调用主要是一些程序性的操作,但应该注意几件事情。本节将介绍几个系统调用的构造,从而展示它们的实现和用户空间应用程序对它们的使用。

    向内核中添加新系统调用,需要执行 3 个基本步骤:

    1. 添加新函数。
    2. 更新头文件。
    3. 针对这个新函数更新系统调用表。

    注意: 这个过程忽略了用户空间的需求,我将稍后介绍。

    最常见的情况是,您会为自己的函数创建一个新文件。不过,为了简单起见,我将自己的新函数添加到现有的源文件中。清单 1 所示的前两个函数,是系统调用的简单示例。清单 2 提供了一个使用指针参数的稍微复杂的函数。


    清单 1. 系统调用示例的简单内核函数

     

    asmlinkage long sys_getjiffies( void )
    {
    return (long)get_jiffies_64();
    }

    asmlinkage long sys_diffjiffies( long ujiffies )
    {
    return (long)get_jiffies_64() - ujiffies;
    }


    在清单 1 中,我们为进行 jiffies 监视提供了两个函数。(有关 jiffies 的更多信息,请参看侧栏 “Kernel jiffies”)。第一个函数会返回当前 jiffy,而第二个函数则返回当前值与所传递进来的值之间的差值。注意 asmlinkage 修饰符的使用。这个宏(在 linux/include/asm-i386/linkage.h 中定义)告诉编译器将传递栈中的所有函数参数。


    清单 2. 系统调用示例的最后内核函数

     

    asmlinkage long sys_pdiffjiffies( long ujiffies, long __user *presult )
    {
    long cur_jiffies = (long)get_jiffies_64();
    long result;
    int err = 0;

    if (presult) {

    result = cur_jiffies - ujiffies;
    err = put_user( result, presult );

    }

    return err ? -EFAULT : 0;
    }


    内核 jiffies

    Linux 内核具有一个名为 jiffies 的全局变量,它代表从机器启动时算起的时间滴答数。这个变量最初被初始化为 0,每次时钟中断时都会加 1。您可以使用 get_jiffies_64 函数来读取 jiffies 的值,然后使用 jiffies_to_msecs 将其换算成毫秒或使用 jiffies_to_usecs 将其换算成微秒。jiffies 的全局定义和相关函数是在 ./linux/include/linux/jiffies.h 中提供的。

    清单 2 给出了第三个函数。这个函数使用了两个参数:一个 long 类型,以及一个指向被定义为 __user 的 long 的指针。__user 宏简单告诉编译器(通过 noderef)不应该解除这个指针的引用(因为在当前地址空间中它是没有意义的)。这个函数会计算这两个 jiffies 值之间的差值,然后通过一个用户空间指针将结果提供给用户。put_user 函数将结果值放入 presult 所指定的用户空间位置。如果在这个操作过程中出现错误,将立即返回,您也可以通知用户空间调用者。

    对于步骤 2 来说,我对头文件进行了更新:在系统调用表中为这几个新函数安排空间。对于本例来说,我使用新系统调用号更新了 linux/include/asm/unistd.h 头文件。更新如清单 3 中的黑体所示。


    清单 3. 更新 unistd.h 文件为新系统调用安排空间

     

    #define __NR_getcpu 318
    #define __NR_epoll_pwait 319
    #define __NR_getjiffies 320
    #define __NR_diffjiffies 321
    #define __NR_pdiffjiffies 322
    #define NR_syscalls 323


    现在已经有了自己的内核系统调用,以及表示这些系统调用的编号。接下来需要做的是要在这些编号(表索引)和函数本身之间建立一种对等关系。这就是第 3 个步骤,更新系统调用表。如清单 4 所示,我将为这个新函数更新 linux/arch/i386/kernel/syscall_table.S 文件,它会填充清单 3 显示的特定索引。


    清单 4. 使用新函数更新系统调用表

     

    .long sys_getcpu
    .long sys_epoll_pwait
    .long sys_getjiffies /* 320 */
    .long sys_diffjiffies
    .long sys_pdiffjiffies


    注意: 这个表的大小是由符号常量 NR_syscalls 定义的。

    现在,我们已经完成了对内核的更新。接下来必须对内核重新进行编译,并在测试用户空间应用程序之前使引导使用的新映像变为可用。

    对用户内存进行读写

    Linux 内核提供了几个函数,可以用来将系统调用参数移动到用户空间中,或从中移出。方法包括一些基本类型的简单函数(例如 get_user 或 put_user)。要移动一块儿数据(如结构或数组),您可以使用另外一组函数: copy_from_user 和 copy_to_user。可以使用专门的调用移动以 null 结尾的字符串: strncpy_from_user 和 strlen_from_user。您也可以通过调用 access_ok 来测试用户空间指针是否有效。这些函数都是在 linux/include/asm/uaccess.h 中定义的。

    您可以使用 access_ok 宏来验证给定操作的用户空间指针。这个函数有 3 个参数,分别是访问类型(VERIFY_READ 或 VERIFY_WRITE),指向用户空间内存块的指针,以及块的大小(单位为字节)。如果成功,这个函数就返回 0:

    int access_ok( type, address, size );


    要在内核和用户空间移动一些简单类型(例如 int 或 long 类型),可以使用 get_user 和 put_user 轻松地实现。这两个宏都包含一个值以及一个指向变量的指针。get_user 函数将用户空间地址(ptr)指定的值移动到所指定的内核变量(var)中。 put_user 函数则将内核变量(var)指定的值移动到用户空间地址(ptr)。 如果成功,这两个函数都返回 0:

    int get_user( var, ptr );
    int put_user( var, ptr );


    要移动更大的对象,例如结构或数组,您可以使用 copy_from_user 和 copy_to_user 函数。这些函数将在用户空间和内核之间移动完整的数据块。 copy_from_user 函数会将一块数据从用户空间移动到内核空间,copy_to_user 则会将一块数据从内核空间移动到用户空间:

    unsigned long copy_from_user( void *to, const void __user *from, unsigned long n );
    unsigned long copy_to_user( void *to, const void __user *from, unsigned long n );

    最后,您可以使用 strncpy_from_user 函数将一个以 NULL 结尾的字符串从用户空间移动到内核空间中。在调用这个函数之前,您可以通过调用 strlen_user 宏来获得用户空间字符串的大小:

    long strncpy_from_user( char *dst, const char __user *src, long count );
    strlen_user( str );

    这些函数为内核和用户空间之间的内存移动提供了基本功能。实际上还可以使用另外一些函数(例如减少执行检查数量的函数)。您可以在 uaccess.h 中找到这些函数。

    使用系统调用

    现在内核已经使用新系统调用完成更新了,接下来看一下从用户空间应用程序中使用这些系统调用需要执行的操作。使用新的内核系统调用有两种方法。第一种方法非常方便(但是在产品代码中您可能并不希望使用),第二种方法是传统方法,需要多做一些工作。

    使用第一种方法,您可以通过 syscall 函数调用由其索引所标识的新函数。使用 syscall 函数,您可以通过指定它的调用索引和一组参数来调用系统调用。例如,清单 5 显示的简单应用程序就使用其索引调用了 sys_getjiffies。


    清单 5. 使用 syscall 调用系统调用


    #include <linux/unistd.h>
    #include <sys/syscall.h>

    #define __NR_getjiffies 320

    int main()
    {
    long jiffies;

    jiffies = syscall( __NR_getjiffies );

    printf( "Current jiffies is %lx\n", jiffies );

    return 0;
    }

    正如您所见,syscall 函数使用了系统调用表中使用的索引作为第一个参数。如果还有其他参数需要传递,可以加在调用索引之后。大部分系统调用都包括了一个 SYS_ 符号常量来指定自己到 __NR_ 索引的映射。例如,使用 syscall 调用 __NR_getpid 索引:

    syscall( SYS_getpid )


    syscall 函数特定于架构,使用一种机制将控制权交给内核。其参数是基于 __NR 索引与 /usr/include/bits/syscall.h 提供的 SYS_ 符号之间的映射(在编译 libc 时定义)。永远都不要直接引用这个文件;而是要使用 /usr/include/sys/syscall.h 文件。

    传统的方法要求我们创建函数调用,这些函数调用必须匹配内核中的系统调用索引(这样就可以调用正确的内核服务),而且参数也必须匹配。Linux 提供了一组宏来提供这种功能。_syscallN 宏是在 /usr/include/linux/unistd.h 中定义的,格式如下:

    _syscall0( ret-type, func-name )
    _syscall1( ret-type, func-name, arg1-type, arg1-name )
    _syscall2( ret-type, func-name, arg1-type, arg1-name, arg2-type, arg2-name )

    用户空间和 __NR 常量

    注意清单 6 中提供了 __NR 符号常量。您可以在 /usr/include/asm/unistd.h 中找到它们(对于标准系统调用来说)。

    _syscall 宏最多可定义 6 个参数(不过此处只显示了 3 个)。

    现在,让我们来看一下如何使用 _syscall 宏来使新系统调用对于用户空间可见。清单 6 显示的应用程序使用了 _syscall 宏定义的所有系统调用。


    清单 6. 将 _syscall 宏 用于用户空间应用程序开发
    #include <stdio.h>
    #include <linux/unistd.h>
    #include <sys/syscall.h>

    #define __NR_getjiffies 320
    #define __NR_diffjiffies 321
    #define __NR_pdiffjiffies 322

    _syscall0( long, getjiffies );
    _syscall1( long, diffjiffies, long, ujiffies );
    _syscall2( long, pdiffjiffies, long, ujiffies, long*, presult );

    int main()
    {
    long jifs, result;
    int err;

    jifs = getjiffies();

    printf( "difference is %lx\n", diffjiffies(jifs) );

    err = pdiffjiffies( jifs, &result );

    if (!err) {
    printf( "difference is %lx\n", result );
    } else {
    printf( "error\n" );
    }

    return 0;
    }

    注意 __NR 索引在这个应用程序中是必需的,因为 _syscall 宏使用了 func-name 来构造 __NR 索引(getjiffies -> __NR_getjiffies)。其结果是您可以使用它们的名字来调用内核函数,就像其他任何系统调用一样。

    用户/内核交互的其他选择

    系统调用是请求内核中服务的一种有效方法。使用这种方法的最大问题就是它是一个标准接口,很难将新的系统调用增加到内核中,因此可以通过其他方法来 实现类似服务。如果您无意将自己的系统调用加入公共的 Linux 内核中,那么系统调用就是将内核服务提供给用户空间的一种方便而且有效的方法。

    让您的服务对用户空间可见的另外一种方法是通过 /proc 文件系统。/proc 文件系统是一个虚拟文件系统,您可以通过它来向用户提供一个目录和文件,然后通过文件系统接口(读、写等)在内核中为新服务提供一个接口。

    使用 strace 跟踪系统调用

    Linux 内核提供了一种非常有用的方法来跟踪某个进程所调用的系统调用(以及该进程所接收到的信号)。这个工具就是 strace,它可以在命令行中执行,使用希望跟踪的应用程序作为参数。例如,如果您希望了解在执行 date 命令时都执行了哪些系统调用,可以键入下面的命令:

    strace date


    结果会产生大量信息,显示在执行 date 命令过程中所执行的各个系统调用。您会看到加载共享库、映射内存,最后跟踪到的是在标准输出中生成日期信息:

    ...
    write(1, "Fri Feb 9 23:06:41 MST 2007\n", 29Fri Feb 9 23:06:41 MST 2007) = 29
    munmap(0xb747a000, 4096) = 0
    exit_group(0) = ?
    $

    当当前系统调用请求具有一个名为 syscall_trace 的特定字段集(它导致 do_syscall_trace 函数的调用)时,将在内核中完成跟踪。您还可以看到跟踪调用是 ./linux/arch/i386/kernel/entry.S 中系统调用请求的一部分(请参看 syscall_trace_entry)。

    结束语

    系统调用是穿越用户空间和内核空间,请求内核空间服务的一种有效方法。不过对这种方法的控制也很严格,更简单的方式是增加一个新的 /proc 文件系统项来提供用户/内核间的交互。不过当速度因素非常重要时,系统调用则是使应用程序获得最佳性能的理想方法。请参看 参考资料 的内容进一步了解 SCI。


    ++++++++++++++++++++++++++++++++++

    深入理解Linux的系统调用 发布时间:2005.06.30 13:18 来源:赛迪网 作者:技术应用

    一、 什么是系统调用

    在Linux的世界里,我们经常会遇到系统调用这一术语,所谓系统调用,就是内核提供的、功能十分强大的一系列的函数。这些系统调用是在内核中实现的,再 通过一定的方式把系统调用给用户,一般都通过门(gate)陷入(trap)实现。系统调用是用户程序和内核交互的接口。

    二、 系统调用的作用

    系统调用在Linux系统中发挥着巨大的作用,如果没有系统调用,那么应用程序就失去了内核的支持。

    我们在编程时用到的很多函数,如fork、open等这些函数最终都是在系统调用里实现的,比如说我们有这样一个程序:


    这里我们用到了两个函数,即fork和exit,这两函数都是glibc中的函数,但是如果我们跟踪函数的执行过程,看看glibc对fork和exit函数的实现就可以发现在glibc的实现代码里都是采用软中断的方式陷入到内核中再通过系统调用实现函数的功能的。具体过程我们在系统调用的实现过程会详 细的讲到。

    由此可见,系统调用是用户接口在内核中的实现,如果没有系统调用,用户就不能利用内核。

    三、 系统调用的现实及调用过程

    详细讲述系统调用的之前也讲一下Linux系统的一些保护机制。

    Linux系统在CPU的保护模式下提供了四个特权级别,目前内核都只用到了其中的两个特权级别,分别为“特权级0”和“特权级3”,级别0也就是我们通 常所讲的内核模式,级别3也就是我们通常所讲的用户模式。划分这两个级别主要是对系统提供保护。内核模式可以执行一些特权指令和进入用户模式,而用户模式 则不能。

    这里特别提出的是,内核模式与用户模式分别使用自己的堆栈,当发生模式切换的时候同时要进行堆栈的切换。

    每个进程都有自己的地址空间(也称为进程空间),进程的地址空间也分为两部分:用户空间和系统空间,在用户模式下只能访问进程的用户空间,在内核模式下则 可以访问进程的全部地址空间,这个地址空间里的地址是一个逻辑地址,通过系统段面式的管理机制,访问的实际内存要做二级地址转换,即:逻辑地址?线性地 址?物理地址。

    系统调用对于内核来说就相当于函数,我们是关键问题是从用户模式到内核模式的转换、堆栈的切换以及参数的传递。

    下面将结合内核源代码对这些过程进行分析,以下分析环境为FC2,kernel 2.6.5

    下面是内核源代码里arch/i386/kernel/entry.S的一段代码。


    以上这段代码里定义了两个非常重要的宏,即SAVE_ALL和RESTORE_ALL

    SAVE_ALL先保存用户模式的寄存器和堆栈信息,然后切换到内核模式,宏__SWITCH_KERNELSPACE实现地址空间的转换RESTORE_ALL的过程过SAVE_ALL的过程正好相反。

    在内核原代码里有一个系统调用表:(entry.S的文件里)


    在2.6.5的内核里,有280多个系统调用,这些系统调用的名称全部在这个系统调用表里。

    在这个原文件里,还有非常重要的一段。


    这一段完成系统调用的执行。

    system_call函数根据用户传来的系统调用号,在系统调用表里找到对应的系统调用再执行。

    从glibc的函数到系统调用还有一个很重要的环节就是系统调用号。

    系统调用号的定义在include/asm-i386/unistd.h里


    每一个系统调用号都对应有一个系统调用

    接下来就是系统调用宏的展开

    没有参数的系统调用的宏展开

    !!!代码6::

    带一个参数的系统调用的宏展开

    !!!代码7::

    两个参数

    代码8::

    #define _syscall2(type,name,type1,arg1,type2,arg2) \

    三个参数的

    代码9::

    #define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \

    四个参数的

    代码10::

    #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \

    五个参数的

    代码11::

    #define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \

    type5,arg5) \

    六个参数的

    代码12::

    #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \

    type5,arg5,type6,arg6) \

    _res); \

    从这段代码我们可以看出int $0x80通过软中断开触发系统调用,当发生调用时,函数中的name会被系统系统调用名所代替。然后调用前面所讲的system_call。这个过程里包含了系统调用的初始化,系统调用的初始化原代码在:

    arch/i386/kernel/traps.c中每当用户执行int 0x80时,系统进行中断处理,把控制权交给内核的system_call。

    整个系统调用的过程可以总结如下:

    1. 执行用户程序(如:fork)

    2. 根据glibc中的函数实现,取得系统调用号并执行int $0x80产生中断。

    3. 进行地址空间的转换和堆栈的切换,执行SAVE_ALL。(进行内核模式)

    4. 进行中断处理,根据系统调用表调用内核函数。

    5. 执行内核函数。

    6. 执行RESTORE_ALL并返回用户模式

    解了系统调用的实现及调用过程,我们可以根据自己的需要来对内核的系统调用作修改或添加。

    ++++++++++++++++++++++++++++++++++

    Linux系统调用接口、系统调用例程和内核服务例程之间的关系
    信息来源: 维库开发网 发布时间:2009年2月17日

      系统调用接口的主要任务是把进程从用户态切换到内核态。在具有保护机制的计算机系 统中,用户必须通过软件中断或陷阱,才能使进程从用户态切换为内核态。

      在i386体系中,Linux的系统调用接口是通过调用软中断指令“int 0x80”使进程从用户态进入内核态的,这个过程也叫做“陷入”。当系统调用接口调用软中断指令“int0x80”时,这个指令会发生一个中断向量码为128的中断请求,并在中断响应过程中将进程由用户态切换为内核态。

      因为Linux只允许系统调用接口使用128这一个软中断向量,这也就意味着所有的系统调用接口必 须共享这一个中断通道,并在同一个中断服务例程中调用不同的内核服务例程,所以,系统调用接口除了要引发“int0x80”软中断之外,为了进人内核后能调用不同的内核服务例程,还要提供识别内核服务例程的参数,这个参数叫做“系统调用号”。也就是说,所有可为进程 提供服务的内核服务例程都应具有一个唯一的系统调用号。当然,系统调用接口还应为内核服务例程准各必要的参数。

      综上所述,系统调用接口需要完成以下几个任务:

      ●用软中断指令“int 0x80”发生一个中断向量码为128的中断请求,以使进程进入内核态。

      ●要保护用户态的现场,即把处理器的用户态运行环境保护到进程的内核堆栈。

      ●为内核服务例程准备参数,并定义返回值的存储位置。

      ●跳转到系统调用例程。

      ●系统调用例程结束后返回。

      系统调用例程是系统提供的一个通用的汇编语言程序.其实它是一个中断向量为128的中断服务程序,其入口为system_call。它应完成的任务有:

      ●接受系统调用接口的参数。

      ●根据系统调用号,转向对应的内核服务例程,并将相关参数传遴给内核服务例程。

      ●在内核服务例程结束后,自中断返田到系统凋甩接口.

      系统调用的过程如图所示。

      从图中可以看到,系统调用接口是用高级语言来编写的,而通过调用中断指令陷入内核后的系统调用例程(即图中的系统调用处理程序)则是用汇编语言编写的。

      为了通过系统调用号来调用不同的内核服务例程,系统必须维护一个系统调用表,这个表实质上就是系统 调用号与内核服务函数的对照表。Linux是用数组sys_call_tabl来作为这个表的,在这个表的每个表项中存放着对应内核服务例程的指针,而该 表项的下标就是该内核服务例程的系统调用号。Linux规定,在1386体系中,系统调用号由处理器的寄存器eax来传递。

    系统调用的处理过程



      图 系统调用的处理过程

      系统调用表Sys_call_table的部分内容列举如下:


      欢迎转载,信息来自维库电子市场网(www.dzsc.com


    ++++++++++++++++++++++++++++++++++

    向linux内核中添加三个系统调用(Ubuntu9.10)

    系统调用是操作系统提供给软件开发人员的唯一接口,开发人员可利用它使用系统功能。OS核心中都有一组实现系统功能的过程(子程序),系统调用就是对上述过程的调用。因此,系统调用像一个黑箱子那样,对用户屏蔽了操作系统的具体动作而只提供有关的功能。

    系统调用在os中发挥着巨大的作用,如果没有系统调用那么应用程序就是失去了内核的支持。在系统中真正被所有进程都使用的内核通信方式是系统调用。例如 当进程请求内核服务时,就使用的是系统调用。一般情况下,进程是不能够存取系统内核的。它不能存取内核使用的内存段,也不能调用内核函数,CPU的硬件结 构保证了这一点。只有系统调用是一个例外。进程使用寄存器中适当的值跳转到内核中事先定义好的代码中执行,(当然,这些代码是只读的)。在Intel结构 的计算机中,这是由中断0x80实现的。

    进程可以跳转到的内核中的位置叫做system_call。在此位置的过程检查系统调用号,它将告诉内核进程请求的服务是什么。然后,它再查找系统调用表sys_call_table,找到希望调用的内核函数的地址,并调用此函数,最后返回。

    所以,如果希望改变一个系统调用的函数,需要做的是编写一个自己的函数,然后改变sys_call_table中的指针指向该函数,最后再使用cleanup_module将系统调用表恢复到原来的状态。我们现在向内核中添加的三个系统调用就是属于向内核中添加新的函数,且这些函数是可以直接操 作系统内核的。下面是系统调用的基本处理过程:





    Linux的系统调用机制

    在Linux系统中,系统调用是作为一种异常类型实现的。它将执行相应的机器代码指令来产生异常信号。产生中断或异常的重要效果是系统自动将用户态切换为 核心态来对它进行处理。这就是说,执行系统调用异常指令时,自动地将系统切换为核心态,并安排异常处理程序的执行。Linux用来实现系统调用异常的实际 指令是:
    Int $0x80

    这 一指令使用中断/异常向量号128(即16进制的80)将控制权转移给内核。为达到在使用系统调用时不必用机器指令编程,在标准的C语言库中为每一系统调 用提供了一段短的子程序,完成机器代码的编程工作。事实上,机器代码段非常简短。它所要做的工作只是将送给系统调用的参数加载到CPU寄存器中,接着执行int $0x80指令。然后运行系统调用,系统调用的返回值将送入CPU的一个寄存器中,标准的库子程序取得这一返回值,并将它送回用户程序。

      为使系统调用的执行成为一项简单的任务,Linux提供了一组预处理宏指令。它们可以用在程序中。这些宏指令取一定的参数,然后扩展为调用指定的系统调用的函数。

      这些宏指令具有类似下面的名称格式:

      _syscallN(parameters)

      其中N是系统调用所需的参数数目,而parameters则用一组参数代替。这些参数使宏指令完成适合于特定的系统调用的扩展。例如,为了建立调用setuid()系统调用的函数,应该使用:

      _syscall1( int, setuid, uid_t, uid )

      syscallN( )宏指令的第1个参数int说明产生的函数的返回值的类型是整型,第2个参数setuid说明产生的函数的名称。后面是系统调用所需要的每个参数。这一宏指令后面还有两个参数uid_t和uid分别用来指定参数的类型和名称。

       另外,用作系统调用的参数的数据类型有一个限制,它们的容量不能超过四个字节。这是因为执行int $0x80指令进行系统调用时,所有的参数值都存在32位的CPU寄存器中。使用CPU寄存器传递参数带来的另一个限制是可以传送给系统调用的参数的数 目。这个限制是最多可以传递5个参数。所以Linux一共定义了6个不同的_syscallN()宏指令,从_syscall0()、 _syscall1()直到_syscall5()。

    一旦_syscallN()宏指令用特定系统调用的相应参数进行了扩展,得到的结果是一个与系统调用同名的函数,它可以在用户程序中执行这一系统调用。

    整个系统调用的过程可以总结如下:
    1, 执行用户程序;
    2, 根据glibc(GNU实现的一套标准C的库函数)中的函数实现,取得系统调 用号并执行 int $0x80产生中断;
    3, 进行地址空间的转换和堆栈的切换,执行SAVE_ALL。(进入内核模式)
    4, 进行中断处理,根据系统调用表调用内核函数;
    5, 执行内核函数;
    6, 执行RESTORE_ALL并返回用户模式;


    系统实现:

    这里以具体的例子来说明如何向系统中添加新的系统调用。具体实现所用的文件等可能与上面所述有点不一致,但原理是相同的。
    1、实验环境:
    实验的环境为Ubuntu9.10系统,内核版本为2.6.31-21-generic。添加完系统调用后的内核版本命名为2.6.31-12。
    2,实验步骤:
    1)下载Linux内核:在终端中输入命令$sudo apt-get install linux-source。下载后的文件默认放在目录/usr/src下。
    2) 将内核代码解压缩:例如下载的内核文件为linux-source-2.6.31.tar.bz2,运行解压命令tar –jxvf linux-source-2.6.31.tar.bz2。解压出的文件夹为/usr/src/linux-source-2.6.31。如下图:

    3) 修改/usr/src/linux-source-2.6.31/kernel/sys.c文件,在文件末尾增加三个系统响应函数。函数实现如下:
    asmlinkage int sys_mycall(int number)
    {
    printk("这是我添加的第一个系统调用");
    return number;
    }
    asmlinkage int sys_addtotal(int number)
    {
    int i=0,enddate=0;
    printk("这是我添加的第二个系统调用");
    while(i<=number)
    enddate+=i++;
    return enddate;
    }
    asmlinkage int sys_three()
    {
    printk("这是我添加的第三个系统调用");
    return 0;
    }

    4)在/usr/src/linux-source-2.6.31/arch/x86/kernel/syscall_table_32.S 中添加:.long sys_mycall。




    5)在/usr/src/linux-2.6.31/arch/x86/include/asm/unistd_32.h中添加:#define __NR_mycall 序号(例如337),添加系统调用的入口参数(注意:其中会顺序定义入口参数的序号,添加的序号是在原有最大值的基础上+1);实现如下:

    编译内核,命令依次如下:
    首先切换到解压的内核目录下。

    第一步:make mrproper //清除内核中不稳定的目标文件,附属文件及内核配置文件
    第二步:make clean //清除以前生成的目标文件和其他文件
    第三步:make oldconfig// 采用默认的内核配置(使用make menuconfig可以自己配置编译选项)
    第四步:make bzImage //编译内核
    第五步:make modules //编译模块
    第六步:make modules_install// 安装模块

    编译完成后,设置采用新内核启动。

    我编译成功的内核版本号命名为2.6.31.12
    运行命令:
    cp /usr/src/linux-source-2.6.31/arch/i386/boot/
    bzImage /boot/vmlinuz-2.6.31.12-mykernel(注意:2.6.31.12为你编译的内核版本。)

    mkinitramfs -o initrd.img-2.6.31.12 2.6.31.12
    //执行目录/usr/src/linux-source-2.6.31/下

    cp /usr/src/linux-source-2.6.31/initrd.img-2.6.31.12 /boot/ initrd.img-2.6.31.12



    增加引导菜单项,配置启动项文件/boot/grub/grub.cfg。添加的配置如下:

    完成后执行终端命令sudo update-grub2,之后重启,终端输入uname -a检查你的内核版本是否是你编译的版本2.6.31.12 。


    编写测试函数:我的测试函数如下:
    /*~~~~~~~~~~~~~~~test1.c~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    #include<stdio.h>

    int main()
    {
    int tmp;
    tmp=syscall(337,1);
    printf("\n");
    if(tmp==1)
    {
    printf("第1次系统调用成功!\n");
    }

    tmp=syscall(338,5);
    printf("\n");
    if(tmp==15)
    {
    printf("第2次系统调用成功!\n");
    }
    tmp=syscall(339);
    printf("\n");
    if(tmp==0)
    {
    printf("第3次系统调用成功!\n");
    }
    }

    编译,运行。在终端输入dmesg -c可显示函数的输出内容。



    总结:
    由于使用了系统调用,编译和执行程序时,用户都应该获得超级用户权限。而且grub.cfg默认是没有写权限的,需要修改是有写权限,为了系统安全,在配置完文件后不要忘了再将权限改回来。

    编译的时间会很长,我成功的一次编译用了接近2.5个小时。需要点耐心。

    如果用虚拟机安装的话,需要注意磁盘空间的大小,我第一次用了个5G个虚拟系统,结果编译到中途就没空间了。10G左右的系统大小估计差不多。

    最后的测试函数可能看不到预想的输出系统,因为printk不会直接打印出来,而是需要命令:dmesg,直接执行会打印出很多东西,但它有一个参数:-c,清除缓存中的系统信息。于是每次用dmesg时,都加上这个参数,结果就只打印我们需要的信息。

    ++++++++++++++++++++++++++++++++++
    展开全文
  • linux系统调用

    千次阅读 2014-12-14 20:42:26
    从原理到具体实现详细解释linux系统调用

    Linux体系结构

    内核空间与用户空间是程序执行的两种不同状态,通过系统调用和硬件中断能够完成从用户空间到内核空间的转移。如下图所示:

    linux 体系结构图


    从上图得知,Linux由用户空间和内核空间

    一般情况下,用户进程是不能访问内核的。它既不能访问内核所在的内存空间,也不能调用内核中的函数。Linux内核中设置了一组用于实现各种系统功能的子程序,用户可以通过调用他们访问linux内核的数据和函数,这些系统调用接口(SCI)称为系统调用。


    系统调用和普通函数的区别:

    系统调用和普通的函数调用非常相似,区别仅仅在于,系统调用由操作系统内核实现,运行于内核态;而普通的函数调用由函数库或用户自己提供,运行于用户态。


    系统调用数:

    在2.6.32 版内核中,共有系统调用365个,可在arch/arm/include/asm/unistd.h中找到它们。

    /* This file contains the system call numbers*/

    #define __NR_restart_syscall (__NR_SYSCALL_BASE+ 0)
    #define __NR_exit (__NR_SYSCALL_BASE+ 1)
    #define __NR_fork (__NR_SYSCALL_BASE+ 2)
    ......

    #define __NR_preadv (__NR_SYSCALL_BASE+361)
    #define __NR_pwritev (__NR_SYSCALL_BASE+362)
    #define __NR_rt_tgsigqueueinfo (__NR_SYSCALL_BASE+363)
    #define __NR_perf_event_open (__NR_SYSCALL_BASE+364)


    系统调用的功能:

    主要分为3大类:

    (1)进程控制类

    fork 创建一个子进程

    clone  按照指定条件创建子进程

    execve 运行可执行文件

    ...

    (2)文件控制操作

    fcntl 文件控制

    open 打开文件

    read 读文件

    ...

    (3)系统控制

    ioctl I/O总控制函数

    reboot重新启动

    —sysctl读写系统参数

    ...


    使用系统调用函数举例:

    下面通过time函数系统调用实现从格林尼治时间1970年1月1日0:00开始到现在的秒数。

    #include<time.h>
    main()
    {
    time_t t_time;
    t_time=time((time_t *)0); /*调用time系统调用*/
    printf("The time is %ld\n",t_time);
    }


    系统调用工作原理:

    一般情况下,用户进程是不能访问内核的。它既不能访问内核所在的内存空间,也不能调用内核中的函数。系
    统调用是一个例外。其原理是(1)进程先用适当的值填充寄存器,(2)然后调用一个特殊的指令,(3)这个指令会让用户程序跳转到一个事先定义好的内核中的一个位置。(4)
    进程可以跳转到的固定的内核位置。这个过程检查系统调用号,这个号码告诉内核进程请求哪种服务。然后,它查看系统调用表(sys_call_table)找到所调用的内核函数入口地址。接着,就调用函数,等返回后,做一些系统检查,最后返回到进程。


    工作原理概述:

    (1)适当的值

    所有适当的值我们都可以在include/asm/unistd.h中找到,在这个文件中为每一个系统调用规定了唯一的编号,叫做系统调用号。

    #define __NR_utimensat (__NR_SYSCALL_BASE+348)
    #define __NR_signalfd (__NR_SYSCALL_BASE+349)
    #define __NR_timerfd_create (__NR_SYSCALL_BASE+350)
    #define __NR_eventfd (__NR_SYSCALL_BASE+351)
    #define __NR_fallocate (__NR_SYSCALL_BASE+352)

    这里面每一个宏就是一个系统调用号

    (2)特殊的指令

    在Intel CPU中,这个指令由中断0x80实现

    在ARM中,这个指令是SWI(softwhere interrupt:软中断指令),现在重新命名为SVC

    (3)固定的位置

    每个CPU固定的位置是不一样的,在ARM体系中这个固定的内核位置是ENTRY(vector_swi)(在arch\sh\kernel\entry-common.S),也就是PC指针会跳转到这个位置

    (4)相应的函数

    内核根据应用程序传递来的系统调用号,从系统调用表sys_call_table找到相应的内核函数

    CALL(sys_restart_syscall)

    CALL(sys_exit)

    CALL(sys_fork_wrapper)


    实例:

    工作原理(应用):下面是一个从用户open调用到找到内核中具体的系统调用函数入口地址的大体流程

    #define __syscall(name) "swi\t" __NR_##name "\n\t“
    int open( const char * pathname, int flags)
    {
    。。。。。。
    __syscall(open);
    。。。。。。
    }
    转化为
    int open( const char * pathname, int flags)
    {
    。。。。。。
    swi\t __NR_open  //#define __NR_open (__NR_SYSCALL_BASE+  5)
    。。。。。。
    }

    //内核入口

    /* arch/arm/kernel/entry-common.S */
    ENTRY(vector_swi)
    …… …… …… ……
    adr tbl, sys_call_table @ load syscall table pointer
    …… …… …… ……
    ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
    …… …… …… ……
    ENTRY(sys_call_table)


    #include "calls.S"

    /* 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_dup3)
    CALL(sys_pipe2)
    /* 360 */CALL(sys_inotify_init1)




    展开全文
  • LINUX系统调用

    千次阅读 2013-09-24 17:40:39
    以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数。这可能是你在互联网上所能看到的唯一一篇中文注释的Linux系统调用列表,即使是简单的字母序英文列表,能做到这么完全也是很...

    以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数。这可能是你在互联网上所能看到的唯一一篇中文注释的Linux系统调用列表,即使是简单的字母序英文列表,能做到这么完全也是很罕见的。

    按照惯例,这个列表以man pages第2节,即系统调用节为蓝本。按照笔者的理解,对其作了大致的分类,同时也作了一些小小的修改,删去了几个仅供内核使用,不允许用户调用的系统调用,对个别本人稍觉不妥的地方作了一些小的修改,并对所有列出的系统调用附上简要注释。

    其中有一些函数的作用完全相同,只是参数不同。(可能很多熟悉C++朋友马上就能联想起函数重载,但是别忘了Linux核心是用C语言写的,所以只能取成不同的函数名)。还有一些函数已经过时,被新的更好的函数所代替了(gcc在链接这些函数时会发出警告),但因为兼容的原因还保留着,这些函数我会在前面标上“*”号以示区别。

    一、进程控制:

    fork 创建一个新进程
    clone 按指定条件创建子进程
    execve 运行可执行文件
    exit 中止进程
    _exit 立即中止当前进程
    getdtablesize 进程所能打开的最大文件数
    getpgid 获取指定进程组标识号
    setpgid 设置指定进程组标志号
    getpgrp 获取当前进程组标识号
    setpgrp 设置当前进程组标志号
    getpid 获取进程标识号
    getppid 获取父进程标识号
    getpriority 获取调度优先级
    setpriority 设置调度优先级
    modify_ldt 读写进程的本地描述表
    nanosleep 使进程睡眠指定的时间
    nice 改变分时进程的优先级
    pause 挂起进程,等待信号
    personality 设置进程运行域
    prctl 对进程进行特定操作
    ptrace 进程跟踪
    sched_get_priority_max 取得静态优先级的上限
    sched_get_priority_min 取得静态优先级的下限
    sched_getparam 取得进程的调度参数
    sched_getscheduler 取得指定进程的调度策略
    sched_rr_get_interval 取得按RR算法调度的实时进程的时间片长度
    sched_setparam 设置进程的调度参数
    sched_setscheduler 设置指定进程的调度策略和参数
    sched_yield 进程主动让出处理器,并将自己等候调度队列队尾
    vfork 创建一个子进程,以供执行新程序,常与execve等同时使用
    wait 等待子进程终止
    wait3 参见wait
    waitpid 等待指定子进程终止
    wait4 参见waitpid
    capget 获取进程权限
    capset 设置进程权限
    getsid 获取会晤标识号
    setsid 设置会晤标识号


    二、文件系统控制


    1、文件读写操作
    fcntl 文件控制
    open 打开文件
    creat 创建新文件
    close 关闭文件描述字
    read 读文件
    write 写文件
    readv 从文件读入数据到缓冲数组中
    writev 将缓冲数组里的数据写入文件
    pread 对文件随机读
    pwrite 对文件随机写
    lseek 移动文件指针
    _llseek 在64位地址空间里移动文件指针
    dup 复制已打开的文件描述字
    dup2 按指定条件复制文件描述字
    flock 文件加/解锁
    poll I/O多路转换
    truncate 截断文件
    ftruncate 参见truncate
    umask 设置文件权限掩码
    fsync 把文件在内存中的部分写回磁盘



    2、文件系统操作
    access 确定文件的可存取性
    chdir 改变当前工作目录
    fchdir 参见chdir
    chmod 改变文件方式
    fchmod 参见chmod
    chown 改变文件的属主或用户组
    fchown 参见chown
    lchown 参见chown
    chroot 改变根目录
    stat 取文件状态信息
    lstat 参见stat
    fstat 参见stat
    statfs 取文件系统信息
    fstatfs 参见statfs
    readdir 读取目录项
    getdents 读取目录项
    mkdir 创建目录
    mknod 创建索引节点
    rmdir 删除目录
    rename 文件改名
    link 创建链接
    symlink 创建符号链接
    unlink 删除链接
    readlink 读符号链接的值
    mount 安装文件系统
    umount 卸下文件系统
    ustat 取文件系统信息
    utime 改变文件的访问修改时间
    utimes 参见utime
    quotactl 控制磁盘配额


    三、系统控制

    ioctl I/O总控制函数
    _sysctl 读/写系统参数
    acct 启用或禁止进程记账
    getrlimit 获取系统资源上限
    setrlimit 设置系统资源上限
    getrusage 获取系统资源使用情况
    uselib 选择要使用的二进制函数库
    ioperm 设置端口I/O权限
    iopl 改变进程I/O权限级别
    outb 低级端口操作
    reboot 重新启动
    swapon 打开交换文件和设备
    swapoff 关闭交换文件和设备
    bdflush 控制bdflush守护进程
    sysfs 取核心支持的文件系统类型
    sysinfo 取得系统信息
    adjtimex 调整系统时钟
    alarm 设置进程的闹钟
    getitimer 获取计时器值
    setitimer 设置计时器值
    gettimeofday 取时间和时区
    settimeofday 设置时间和时区
    stime 设置系统日期和时间
    time 取得系统时间
    times 取进程运行时间
    uname 获取当前UNIX系统的名称、版本和主机等信息
    vhangup 挂起当前终端
    nfsservctl 对NFS守护进程进行控制
    vm86 进入模拟8086模式
    create_module 创建可装载的模块项
    delete_module 删除可装载的模块项
    init_module 初始化模块
    query_module 查询模块信息
    *get_kernel_syms 取得核心符号,已被query_module代替


    四、内存管理

    brk 改变数据段空间的分配
    sbrk 参见brk
    mlock 内存页面加锁
    munlock 内存页面解锁
    mlockall 调用进程所有内存页面加锁
    munlockall 调用进程所有内存页面解锁
    mmap 映射虚拟内存页
    munmap 去除内存页映射
    mremap 重新映射虚拟内存地址
    msync 将映射内存中的数据写回磁盘
    mprotect 设置内存映像保护
    getpagesize 获取页面大小
    sync 将内存缓冲区数据写回硬盘
    cacheflush 将指定缓冲区中的内容写回磁盘


    五、网络管理

    getdomainname 取域名
    setdomainname 设置域名
    gethostid 获取主机标识号
    sethostid 设置主机标识号
    gethostname 获取本主机名称
    sethostname 设置主机名称


    六、socket控制

    socketcall socket系统调用
    socket 建立socket
    bind 绑定socket到端口
    connect 连接远程主机
    accept 响应socket连接请求
    send 通过socket发送信息
    sendto 发送UDP信息
    sendmsg 参见send
    recv 通过socket接收信息
    recvfrom 接收UDP信息
    recvmsg 参见recv
    listen 监听socket端口
    select 对多路同步I/O进行轮询
    shutdown 关闭socket上的连接
    getsockname 取得本地socket名字
    getpeername 获取通信对方的socket名字
    getsockopt 取端口设置
    setsockopt 设置端口参数
    sendfile 在文件或端口间传输数据
    socketpair 创建一对已联接的无名socket


    七、用户管理

    getuid 获取用户标识号
    setuid 设置用户标志号
    getgid 获取组标识号
    setgid 设置组标志号
    getegid 获取有效组标识号
    setegid 设置有效组标识号
    geteuid 获取有效用户标识号
    seteuid 设置有效用户标识号
    setregid 分别设置真实和有效的的组标识号
    setreuid 分别设置真实和有效的用户标识号
    getresgid 分别获取真实的,有效的和保存过的组标识号
    setresgid 分别设置真实的,有效的和保存过的组标识号
    getresuid 分别获取真实的,有效的和保存过的用户标识号
    setresuid 分别设置真实的,有效的和保存过的用户标识号
    setfsgid 设置文件系统检查时使用的组标识号
    setfsuid 设置文件系统检查时使用的用户标识号
    getgroups 获取后补组标志清单
    setgroups 设置后补组标志清单


    八、进程间通信

    ipc 进程间通信总控制调用



    1、信号
    sigaction 设置对指定信号的处理方法
    sigprocmask 根据参数对信号集中的信号执行阻塞/解除阻塞等操作
    sigpending 为指定的被阻塞信号设置队列
    sigsuspend 挂起进程等待特定信号
    signal 参见signal
    kill 向进程或进程组发信号
    *sigblock 向被阻塞信号掩码中添加信号,已被sigprocmask代替
    *siggetmask 取得现有阻塞信号掩码,已被sigprocmask代替
    *sigsetmask 用给定信号掩码替换现有阻塞信号掩码,已被sigprocmask代替
    *sigmask 将给定的信号转化为掩码,已被sigprocmask代替
    *sigpause 作用同sigsuspend,已被sigsuspend代替
    sigvec 为兼容BSD而设的信号处理函数,作用类似sigaction
    ssetmask ANSI C的信号处理函数,作用类似sigaction



    2、消息
    msgctl 消息控制操作
    msgget 获取消息队列
    msgsnd 发消息
    msgrcv 取消息



    3、管道
    pipe 创建管道



    4、信号量
    semctl 信号量控制
    semget 获取一组信号量
    semop 信号量操作



    5、共享内存
    shmctl 控制共享内存
    shmget 获取共享内存
    shmat 连接共享内存
    shmdt 拆卸共享内存

    参考资料

    • Linux man pages

    • Advanced Programming in the UNIX Environment, W. Richard Stevens, 1993

    展开全文
  • linux 系统调用

    千次阅读 2013-05-14 09:33:07
    在分析linux 系统调用 的代码时,如何快速找到 open/read/write 标准库系统调用对应的内核处理函数呢? 查找系统调用函数定义的宏: SYSCALL_DEFINE eg, 在vim 中直接 :cs f t SYSCALL_DEFINE, 这样搜索结果...

    在分析linux 系统调用 的代码时,如何快速找到 open/read/write 标准库系统调用对应的内核处理函数呢?

    查找系统调用函数定义的宏:   SYSCALL_DEFINE

    eg, 在vim 中直接 :cs f t SYSCALL_DEFINE, 这样搜索结果也不过300多个,可以快速找到自己想找的系统调用函数。

    open 系统调用对应的内核函数在 fs/open.c 中:

    SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
    {
            long ret; 
    
            if (force_o_largefile())
                    flags |= O_LARGEFILE;
    
            ret = do_sys_open(AT_FDCWD, filename, flags, mode);
            /* avoid REGPARM breakage on x86: */
            asmlinkage_protect(3, ret, filename, flags, mode);
            return ret; 
    }
    

    read/write 系统调用对应的内核函数在 fs/read_write.c 中:


    SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
    {
            struct file *file;
            ssize_t ret = -EBADF;
            int fput_needed;
    
            file = fget_light(fd, &fput_needed);
            if (file) {
                    loff_t pos = file_pos_read(file);
                    ret = vfs_read(file, buf, count, &pos);
                    trace_fs_read(fd, buf, count, ret);
                    file_pos_write(file, pos);
                    fput_light(file, fput_needed);
            }   
    
            return ret;
    }
    
    SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
                    size_t, count)
    {
            struct file *file;
            ssize_t ret = -EBADF;
            int fput_needed;
    
            file = fget_light(fd, &fput_needed);
            if (file) {
                    loff_t pos = file_pos_read(file);
                    ret = vfs_write(file, buf, count, &pos);
                    trace_fs_write(fd, buf, count, ret);
                    file_pos_write(file, pos);
                    fput_light(file, fput_needed);
            }
    
            return ret;
    }
    

    参考文章:

    http://blog.csdn.net/adaptiver/article/details/7175165


    展开全文
  • 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系统调用列表

    千次阅读 2016-04-01 10:59:39
    Linux系统调用列表 以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数。这可能是你在互联网上所能看到的唯一一篇中文注释的Linux系统调用列表,即使是简单的字母序英文列表,...
  • Linux系统调用:使用syscall

    千次阅读 2018-04-21 22:46:55
    博主的另一篇博文介绍了如何使用 int 0x80 指令进行Linux系统调用,这一篇博文介绍一下如何使用另一种方式: syscall 指令进行Linux系统调用,然后会简要说明二者的不同。 Linux系统调用:使用 syscall 通过 ...
  • 【Linux系统编程】Linux系统调用

    千次阅读 多人点赞 2019-09-22 20:52:34
    用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务,比如用户可以通过文件系统相关的调用请求系统打开文件、关闭文件或读写文件,可以通过时钟相关的系统调用获得系统时间或设置定时器等。 ...
  • Linux系统调用函数列表

    千次阅读 2017-08-30 10:24:19
    以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数。这可能是你在互联网上所能看到的唯一一篇中文注释的Linux系统调用列表,即使是简单的字母序英文列表,能做到这么完全也是很...
  • Linux系统调用过程分析

    千次阅读 2019-11-03 23:23:49
    本文以write()来简要分析一下Linux系统调用过程: write系统调用: 函数定义如下: lib/write.c 其中_syscall是一个宏定义, 内容类似如下: include/unistd.h 其实就相当于int write(int fd, const char *buf, off_...
  • linux系统调用原理及实现

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

    千次阅读 2017-04-14 18:21:54
    学习linux系统调用时先明确一个概念:一般情况下,应用程序通过应用编程接口API而不是直接通过系统调用来编程 应用编程接口API与系统调用的关系如下:(应用程序编程接口实际上并不需要和内核提供的系统调用对应)...
  • 当然系统调用函数肯定不同于库函数,接下来我将讲解Linux中的系统调用过程。 下图是软硬件的简单关系。 库函数:调用在用户态,执行在用户态 系统调用函数,调用在用户态,执行在内核态。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 43,903
精华内容 17,561
关键字:

linux系统调用

linux 订阅