系统调用可以发生在 C 程序中吗?
Can a system call happen in a C program?
C 程序中可以进行系统调用吗?
考虑一下:
int main()
{
int f = open("/tmp/test.txt", O_CREAT | O_RDWR, 0666);
write(f, "hello world", 11);
close(f);
return 0;
}
在此示例代码中,open
、write
和close
是库函数。在我的搜索过程中,我得出结论,它们是函数而不是系统调用。这些函数(open
、write
和 close
)中的每一个都进行系统调用。
问题
- 首先我的结论是否正确?
- C 程序中可以进行系统调用吗?
- 如果系统调用可以在 C 程序中发生,它们会在什么时候发生?请举个例子。
- 是否可以通过编译选项来控制使用库函数还是直接进行系统调用?例如,我们是否可以用一些选项编译上面的程序,以便直接进行
write
和 read
系统调用,如果我们用不同的选项编译它,它会调用库函数?
直接在系统调用中编程是一项乏味且非常艰巨的任务。调用printf、sprintf、vprintf、fopen、open等,都是用到里面的系统调用。 Glibc 库已经为您实现了这些系统调用的包装器。
如果你想直接使用系统调用,假设你正在使用Linux,包括sys/syscall.h
,你可以直接调用syscall
函数。请注意,这不太安全,仅当您的 libc 实现没有该系统调用的包装器时才应使用。
“在计算中,系统调用是一种编程方式,计算机程序通过这种方式向执行它的操作系统的内核请求服务。这可能包括与硬件相关的服务(例如,访问硬盘驱动器),新进程的创建和执行,以及与进程调度等集成内核服务的通信。系统调用提供了进程和操作系统之间的基本接口。” - Wikipedia
close
系统调用是内核用来关闭文件描述符的系统调用。对于大多数文件系统,程序使用关闭系统调用终止对文件系统中文件的访问。
int close(int fd);
close()
关闭一个文件描述符,这样它就不再引用任何文件并且可以被重用。
如您所见,当您需要访问内核中的某些内容时使用系统调用 space 并且这仅可能是系统调用。
系统调用后台
根据 Wikipedia,系统调用是一种“计算机程序从其执行所在的操作系统内核请求服务的编程方式”。
另一种理解系统调用的方式是user space program making a request to the operating system kernel代表用户space程序执行一些任务。内核提供的全套系统调用类似于(在某些方面)内核向用户 space.
提供的 API
由于系统调用是内核的低级接口,因此正确提供它们的参数可能容易出错甚至是危险的。由于这些原因,C 库作者为内核的系统调用集的重要部分提供了更简单、更安全的包装函数。
这些包装函数采用简化的参数集,然后导出适当的值以传递给内核,以便可以执行系统调用。
例子
注意:此示例基于在Linux 上使用gcc
编译和运行ning 一个C 程序。系统调用、库函数和输出在其他 POSIX 或非 POSIX 操作系统上可能不同。
我将尝试通过一个简单的示例来展示如何查看何时进行系统调用。
#include <stdio.h>
int main() {
write(1, "Hello world!\n", 13);
}
上面我们有一个非常简单的 C 程序,它将字符串 Hello world!\n
写入 stdout
。如果我们用 strace
编译然后执行这个程序,我们会看到以下内容(注意输出在其他计算机上可能看起来不同):
$ strace ./hello > /dev/null
execve("./hello", ["./hello"], 0x7fff083a0630 /* 58 vars */) = 0
<a bunch of output we aren't interested in>
write(1, "Hello world!\n", 13) = 13
exit_group(0) = ?
+++ exited with 0 +++
strace
是一个 Linux 程序,它拦截并显示程序发出的所有系统调用,以及提供给系统调用的参数及其 return 值。
我们可以在这里看到,正如预期的那样,write
系统调用是使用预期参数进行的。还没有什么奇怪的。
另一个 Linux 跟踪程序是 ltrace
,它拦截程序进行的动态库调用,并显示它们的参数和 return 值。
如果我们 运行 与 ltrace
相同的程序,我们会看到:
$ ltrace ./hello > /dev/null
write(1, "Hello world!\n", 13) = 13
+++ exited (status 0) +++
这告诉我们执行了write
库函数。这意味着 C 代码首先调用 write
库函数,然后依次调用 write
系统调用。
现在假设我们要显式地进行 write
系统调用而不调用 write
库函数。 (这在正常使用中是不可取的,但有助于说明。)
这是新代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
int main() {
syscall(SYS_write, 1, "Hello world!\n", 13);
}
这里我们直接调用syscall
库函数,告诉它我们要执行write
系统调用
重新编译后,这里是strace
的输出:
$ strace ./hello > /dev/null
execve("./hello", ["./hello"], 0x7ffe3790a660 /* 58 vars */) = 0
<a bunch of output we aren't interested in>
write(1, "Hello world!\n", 13) = 13
exit_group(0) = ?
+++ exited with 0 +++
我们可以看到 write
系统调用如期进行。
如果我们 运行 ltrace
我们会看到以下内容:
$ ltrace ./hello > /dev/null
syscall(1, 1, 0x560b30e4d704, 13) = 13
+++ exited (status 0) +++
所以write
库函数不再被调用,但我们仍在进行库函数调用。现在我们正在调用 syscall
库函数而不是 write
库函数。
可能有一种方法可以直接从用户space C程序中进行系统调用而不调用任何库函数,如果有这种方法我相信它会很先进。
检测 C 程序何时进行系统调用
一般来说,几乎每个重要的 C 程序都至少进行一次系统调用。这是因为用户 space 没有直接访问内核内存或计算机硬件的权限。用户 space 程序可以通过系统调用间接访问内核内存和硬件。
要识别已编译的 C 程序(或 Linux 上的任何其他程序)是否进行系统调用,并识别它进行了哪些系统调用,只需使用 strace
.
是否有编译器选项来防止为系统调用调用库包装函数?
您可以使用 -nostdlib
选项编译您的 C 程序(假设您使用的是 gcc
)。这将阻止链接 C 标准库作为生成可执行文件的一部分。但是,那么您将需要编写自己的代码来进行系统调用。
C 程序中可以进行系统调用吗? 考虑一下:
int main()
{
int f = open("/tmp/test.txt", O_CREAT | O_RDWR, 0666);
write(f, "hello world", 11);
close(f);
return 0;
}
在此示例代码中,open
、write
和close
是库函数。在我的搜索过程中,我得出结论,它们是函数而不是系统调用。这些函数(open
、write
和 close
)中的每一个都进行系统调用。
问题
- 首先我的结论是否正确?
- C 程序中可以进行系统调用吗?
- 如果系统调用可以在 C 程序中发生,它们会在什么时候发生?请举个例子。
- 是否可以通过编译选项来控制使用库函数还是直接进行系统调用?例如,我们是否可以用一些选项编译上面的程序,以便直接进行
write
和read
系统调用,如果我们用不同的选项编译它,它会调用库函数?
直接在系统调用中编程是一项乏味且非常艰巨的任务。调用printf、sprintf、vprintf、fopen、open等,都是用到里面的系统调用。 Glibc 库已经为您实现了这些系统调用的包装器。
如果你想直接使用系统调用,假设你正在使用Linux,包括sys/syscall.h
,你可以直接调用syscall
函数。请注意,这不太安全,仅当您的 libc 实现没有该系统调用的包装器时才应使用。
“在计算中,系统调用是一种编程方式,计算机程序通过这种方式向执行它的操作系统的内核请求服务。这可能包括与硬件相关的服务(例如,访问硬盘驱动器),新进程的创建和执行,以及与进程调度等集成内核服务的通信。系统调用提供了进程和操作系统之间的基本接口。” - Wikipedia
close
系统调用是内核用来关闭文件描述符的系统调用。对于大多数文件系统,程序使用关闭系统调用终止对文件系统中文件的访问。
int close(int fd);
close()
关闭一个文件描述符,这样它就不再引用任何文件并且可以被重用。
如您所见,当您需要访问内核中的某些内容时使用系统调用 space 并且这仅可能是系统调用。
系统调用后台
根据 Wikipedia,系统调用是一种“计算机程序从其执行所在的操作系统内核请求服务的编程方式”。
另一种理解系统调用的方式是user space program making a request to the operating system kernel代表用户space程序执行一些任务。内核提供的全套系统调用类似于(在某些方面)内核向用户 space.
提供的 API由于系统调用是内核的低级接口,因此正确提供它们的参数可能容易出错甚至是危险的。由于这些原因,C 库作者为内核的系统调用集的重要部分提供了更简单、更安全的包装函数。
这些包装函数采用简化的参数集,然后导出适当的值以传递给内核,以便可以执行系统调用。
例子
注意:此示例基于在Linux 上使用gcc
编译和运行ning 一个C 程序。系统调用、库函数和输出在其他 POSIX 或非 POSIX 操作系统上可能不同。
我将尝试通过一个简单的示例来展示如何查看何时进行系统调用。
#include <stdio.h>
int main() {
write(1, "Hello world!\n", 13);
}
上面我们有一个非常简单的 C 程序,它将字符串 Hello world!\n
写入 stdout
。如果我们用 strace
编译然后执行这个程序,我们会看到以下内容(注意输出在其他计算机上可能看起来不同):
$ strace ./hello > /dev/null
execve("./hello", ["./hello"], 0x7fff083a0630 /* 58 vars */) = 0
<a bunch of output we aren't interested in>
write(1, "Hello world!\n", 13) = 13
exit_group(0) = ?
+++ exited with 0 +++
strace
是一个 Linux 程序,它拦截并显示程序发出的所有系统调用,以及提供给系统调用的参数及其 return 值。
我们可以在这里看到,正如预期的那样,write
系统调用是使用预期参数进行的。还没有什么奇怪的。
另一个 Linux 跟踪程序是 ltrace
,它拦截程序进行的动态库调用,并显示它们的参数和 return 值。
如果我们 运行 与 ltrace
相同的程序,我们会看到:
$ ltrace ./hello > /dev/null
write(1, "Hello world!\n", 13) = 13
+++ exited (status 0) +++
这告诉我们执行了write
库函数。这意味着 C 代码首先调用 write
库函数,然后依次调用 write
系统调用。
现在假设我们要显式地进行 write
系统调用而不调用 write
库函数。 (这在正常使用中是不可取的,但有助于说明。)
这是新代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
int main() {
syscall(SYS_write, 1, "Hello world!\n", 13);
}
这里我们直接调用syscall
库函数,告诉它我们要执行write
系统调用
重新编译后,这里是strace
的输出:
$ strace ./hello > /dev/null
execve("./hello", ["./hello"], 0x7ffe3790a660 /* 58 vars */) = 0
<a bunch of output we aren't interested in>
write(1, "Hello world!\n", 13) = 13
exit_group(0) = ?
+++ exited with 0 +++
我们可以看到 write
系统调用如期进行。
如果我们 运行 ltrace
我们会看到以下内容:
$ ltrace ./hello > /dev/null
syscall(1, 1, 0x560b30e4d704, 13) = 13
+++ exited (status 0) +++
所以write
库函数不再被调用,但我们仍在进行库函数调用。现在我们正在调用 syscall
库函数而不是 write
库函数。
可能有一种方法可以直接从用户space C程序中进行系统调用而不调用任何库函数,如果有这种方法我相信它会很先进。
检测 C 程序何时进行系统调用
一般来说,几乎每个重要的 C 程序都至少进行一次系统调用。这是因为用户 space 没有直接访问内核内存或计算机硬件的权限。用户 space 程序可以通过系统调用间接访问内核内存和硬件。
要识别已编译的 C 程序(或 Linux 上的任何其他程序)是否进行系统调用,并识别它进行了哪些系统调用,只需使用 strace
.
是否有编译器选项来防止为系统调用调用库包装函数?
您可以使用 -nostdlib
选项编译您的 C 程序(假设您使用的是 gcc
)。这将阻止链接 C 标准库作为生成可执行文件的一部分。但是,那么您将需要编写自己的代码来进行系统调用。