syscall系统调用及示例

syscall函数详解

syscall函数是Linux系统中访问系统调用的通用接口,它是用户程序与Linux内核之间的重要桥梁。可以将syscall想象成一个”万能钥匙”,它允许程序直接调用内核提供的各种底层功能,如文件操作、进程控制、网络通信、内存管理等。

  1. 函数介绍

syscall函数是Linux系统中访问系统调用的通用接口,它是用户程序与Linux内核之间的重要桥梁。可以将syscall想象成一个”万能钥匙”,它允许程序直接调用内核提供的各种底层功能,如文件操作、进程控制、网络通信、内存管理等。

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

在Linux系统中,所有的系统功能都是通过系统调用来实现的。当你调用像open()、read()、write()这样的函数时,它们实际上都是通过syscall来与内核交互的。syscall函数提供了一种直接访问这些底层功能的方式,让你可以调用那些可能还没有标准C库封装的新系统调用。

使用场景:

  • 访问新的或非标准的系统调用

  • 学习和理解系统调用机制

  • 实现系统级编程和调试工具

  • 调用特定架构的系统调用

  • 性能敏感的应用程序直接调用内核功能

  1. 函数原型
1
2
3
4
5
#include <unistd.h>
#include <sys/syscall.h>

long syscall(long number, ...);

  1. 功能

syscall函数的主要功能是直接调用Linux内核提供的系统调用。它绕过了标准C库的封装层,直接与内核交互,这使得它能够访问所有可用的系统调用,包括那些还没有被标准库函数封装的调用。

  1. 参数

number: 系统调用号

  • 类型:long

  • 含义:要调用的系统调用的唯一标识号,每个系统调用都有一个固定的编号

…: 可变参数

  • 类型:可变参数列表

  • 含义:传递给系统调用的参数,参数的数量和类型取决于具体的系统调用

  1. 返回值
  • 成功: 返回系统调用的返回值(通常是long类型)

失败: 返回-1,并设置errno错误码

  • 各种错误码取决于具体的系统调用和错误情况
  1. 相似函数或关联函数
  • 系统调用封装函数: 如open(), read(), write(), fork(), exec()等

  • syscall()的特定封装: 如tgkill(), gettid()等

  • 内联汇编: 直接使用汇编指令调用系统调用

  • ioctl(): 设备控制的系统调用

  • 系统调用表: /usr/include/asm/unistd.h中定义的系统调用号

  1. 示例代码

示例1:基础syscall使用 - 常用系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main() {
// 示例1: 使用syscall调用getpid()系统调用
printf("=== 使用syscall调用getpid ===\n");
pid_t pid = syscall(SYS_getpid);
printf("进程ID: %d\n", pid);

// 示例2: 使用syscall调用getppid()系统调用
printf("\n=== 使用syscall调用getppid ===\n");
pid_t ppid = syscall(SYS_getppid);
printf("父进程ID: %d\n", ppid);

// 示例3: 使用syscall调用getuid()系统调用
printf("\n=== 使用syscall调用getuid ===\n");
uid_t uid = syscall(SYS_getuid);
printf("用户ID: %d\n", uid);

// 示例4: 使用syscall调用geteuid()系统调用
printf("\n=== 使用syscall调用geteuid ===\n");
uid_t euid = syscall(SYS_geteuid);
printf("有效用户ID: %d\n", euid);

// 示例5: 使用syscall调用gettid()系统调用
printf("\n=== 使用syscall调用gettid ===\n");
pid_t tid = syscall(SYS_gettid);
printf("线程ID: %d\n", tid);

// 示例6: 使用syscall进行文件操作
printf("\n=== 使用syscall进行文件操作 ===\n");

// 创建或打开文件
const char* filename = "syscall_test.txt";
int fd = syscall(SYS_open, filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if(fd == -1) {
perror("打开文件失败");
return 1;
}
printf("文件描述符: %d\n", fd);

// 写入数据
const char* message = "Hello from syscall!\n";
ssize_t bytes_written = syscall(SYS_write, fd, message, strlen(message));
if(bytes_written == -1) {
perror("写入文件失败");
} else {
printf("写入字节数: %zd\n", bytes_written);
}

// 关闭文件
int close_result = syscall(SYS_close, fd);
if(close_result == -1) {
perror("关闭文件失败");
} else {
printf("文件已关闭\n");
}

// 示例7: 使用syscall读取文件
printf("\n=== 使用syscall读取文件 ===\n");
fd = syscall(SYS_open, filename, O_RDONLY);
if(fd != -1) {
char buffer&#91;256];
ssize_t bytes_read = syscall(SYS_read, fd, buffer, sizeof(buffer) - 1);
if(bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("读取内容: %s", buffer);
}
syscall(SYS_close, fd);
}

// 清理测试文件
syscall(SYS_unlink, filename);

return 0;
}

示例2:高级syscall使用 - 进程和内存管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>

int main() {
printf("=== 高级syscall使用示例 ===\n");

// 示例1: 使用mmap系统调用
printf("\n1. 使用mmap进行内存映射:\n");
void* mapped_memory = (void*)syscall(SYS_mmap,
NULL, // 地址
4096, // 长度
PROT_READ | PROT_WRITE, // 保护
MAP_PRIVATE | MAP_ANONYMOUS, // 标志
-1, // 文件描述符
0); // 偏移

if(mapped_memory == MAP_FAILED) {
perror("mmap失败");
} else {
printf("成功映射内存地址: %p\n", mapped_memory);

// 在映射的内存中写入数据
strcpy((char*)mapped_memory, "Hello from mapped memory!");
printf("内存内容: %s\n", (char*)mapped_memory);

// 取消内存映射
syscall(SYS_munmap, mapped_memory, 4096);
printf("内存映射已取消\n");
}

// 示例2: 使用brk系统调用调整程序断点
printf("\n2. 使用brk调整程序断点:\n");
void* current_brk = (void*)syscall(SYS_brk, 0);
printf("当前程序断点: %p\n", current_brk);

// 扩展堆空间
void* new_brk = (void*)syscall(SYS_brk, (long)current_brk + 8192);
if(new_brk != (void*)-1) {
printf("新程序断点: %p\n", new_brk);
printf("堆空间扩展了 %ld 字节\n", (long)new_brk - (long)current_brk);

// 恢复原来的断点
syscall(SYS_brk, (long)current_brk);
}

// 示例3: 使用fork系统调用创建子进程
printf("\n3. 使用fork创建子进程:\n");
pid_t pid = syscall(SYS_fork);

if(pid == -1) {
perror("fork失败");
} else if(pid == 0) {
// 子进程
printf("子进程: PID=%d, PPID=%d\n",
syscall(SYS_getpid), syscall(SYS_getppid));
exit(0);
} else {
// 父进程
printf("父进程: PID=%d, 创建了子进程 PID=%d\n",
syscall(SYS_getpid), pid);
// 等待子进程结束
int status;
syscall(SYS_wait4, pid, &status, 0, NULL);
printf("子进程已结束\n");
}

// 示例4: 使用gettimeofday系统调用获取时间
printf("\n4. 使用gettimeofday获取时间:\n");
struct {
long tv_sec;
long tv_usec;
} tv;

int result = syscall(SYS_gettimeofday, &tv, NULL);
if(result == 0) {
printf("当前时间: %ld 秒 %ld 微秒\n", tv.tv_sec, tv.tv_usec);
} else {
perror("gettimeofday失败");
}

// 示例5: 使用uname系统调用获取系统信息
printf("\n5. 使用uname获取系统信息:\n");
struct {
char sysname&#91;65];
char nodename&#91;65];
char release&#91;65];
char version&#91;65];
char machine&#91;65];
} uts;

result = syscall(SYS_uname, &uts);
if(result == 0) {
printf("系统名称: %s\n", uts.sysname);
printf("节点名称: %s\n", uts.nodename);
printf("内核版本: %s\n", uts.release);
printf("系统版本: %s\n", uts.version);
printf("硬件架构: %s\n", uts.machine);
} else {
perror("uname失败");
}

return 0;
}

示例3:自定义系统调用和错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <string.h>

// 通用的syscall错误处理函数
void handle_syscall_error(const char* syscall_name) {
fprintf(stderr, "系统调用 %s 失败: ", syscall_name);
switch(errno) {
case EPERM:
fprintf(stderr, "操作不被允许\n");
break;
case ENOENT:
fprintf(stderr, "文件或目录不存在\n");
break;
case EACCES:
fprintf(stderr, "权限不足\n");
break;
case EFAULT:
fprintf(stderr, "地址错误\n");
break;
case EINVAL:
fprintf(stderr, "无效参数\n");
break;
case ENOMEM:
fprintf(stderr, "内存不足\n");
break;
case EBUSY:
fprintf(stderr, "资源忙\n");
break;
default:
fprintf(stderr, "%s\n", strerror(errno));
break;
}
}

// 获取系统调用名称的辅助函数
const char* get_syscall_name(long syscall_num) {
switch(syscall_num) {
case SYS_getpid: return "getpid";
case SYS_getppid: return "getppid";
case SYS_getuid: return "getuid";
case SYS_geteuid: return "geteuid";
case SYS_getgid: return "getgid";
case SYS_getegid: return "getegid";
default: return "unknown";
}
}

// 安全的syscall包装函数
long safe_syscall(long number, const char* name) {
errno = 0; // 清除之前的错误
long result = syscall(number);

if(result == -1) {
handle_syscall_error(name);
}

return result;
}

int main() {
printf("=== 自定义系统调用封装和错误处理 ===\n");

// 测试各种系统调用
struct {
long number;
const char* name;
const char* description;
} syscalls&#91;] = {
{SYS_getpid, "getpid", "获取进程ID"},
{SYS_getppid, "getppid", "获取父进程ID"},
{SYS_getuid, "getuid", "获取用户ID"},
{SYS_geteuid, "geteuid", "获取有效用户ID"},
{SYS_getgid, "getgid", "获取组ID"},
{SYS_getegid, "getegid", "获取有效组ID"}
};

int num_syscalls = sizeof(syscalls) / sizeof(syscalls&#91;0]);

printf("\n系统信息获取测试:\n");
printf("----------------------------\n");

for(int i = 0; i < num_syscalls; i++) {
printf("%-10s: ", syscalls&#91;i].description);
long result = safe_syscall(syscalls&#91;i].number, syscalls&#91;i].name);
if(result != -1) {
printf("%ld\n", result);
}
}

// 测试错误处理 - 调用不存在的系统调用
printf("\n错误处理测试:\n");
printf("----------------------------\n");
printf("调用不存在的系统调用 (编号999999):\n");
errno = 0;
long result = syscall(999999);
if(result == -1) {
handle_syscall_error("invalid_syscall");
}

// 测试权限相关的系统调用
printf("\n权限相关测试:\n");
printf("----------------------------\n");

// 获取当前工作目录 (使用getcwd系统调用)
printf("当前工作目录测试:\n");
char cwd&#91;1024];
errno = 0;
long cwd_result = syscall(SYS_getcwd, cwd, sizeof(cwd));
if(cwd_result != -1) {
printf("当前目录: %s\n", cwd);
} else {
handle_syscall_error("getcwd");
}

// 测试系统资源使用情况
printf("\n系统资源使用测试:\n");
printf("----------------------------\n");

struct {
long ru_utime_sec; // 用户CPU时间(秒)
long ru_utime_usec; // 用户CPU时间(微秒)
long ru_stime_sec; // 系统CPU时间(秒)
long ru_stime_usec; // 系统CPU时间(微秒)
long ru_maxrss; // 最大常驻集大小
long ru_ixrss; // 共享内存大小
long ru_idrss; // 非共享数据大小
long ru_isrss; // 非共享栈大小
long ru_minflt; // 页错误(次要)
long ru_majflt; // 页错误(主要)
long ru_nswap; // 交换次数
long ru_inblock; // 输入操作数
long ru_oublock; // 输出操作数
long ru_msgsnd; // 消息发送数
long ru_msgrcv; // 消息接收数
long ru_nsignals; // 信号接收数
long ru_nvcsw; // 自愿上下文切换
long ru_nivcsw; // 非自愿上下文切换
} usage;

errno = 0;
result = syscall(SYS_getrusage, RUSAGE_SELF, &usage);
if(result == 0) {
printf("用户CPU时间: %ld.%06ld 秒\n", usage.ru_utime_sec, usage.ru_utime_usec);
printf("系统CPU时间: %ld.%06ld 秒\n", usage.ru_stime_sec, usage.ru_stime_usec);
printf("最大内存使用: %ld KB\n", usage.ru_maxrss);
} else {
handle_syscall_error("getrusage");
}

// 演示syscall的优势 - 访问新系统调用
printf("\n新系统调用演示:\n");
printf("----------------------------\n");
printf("注意: 某些新系统调用可能在当前内核版本中不可用\n");

// 尝试调用eventfd系统调用(如果可用)
errno = 0;
result = syscall(SYS_eventfd, 0);
if(result != -1) {
printf("eventfd系统调用成功,文件描述符: %ld\n", result);
close(result); // 关闭文件描述符
} else {
printf("eventfd系统调用不可用或失败\n");
}

printf("\n=== 测试完成 ===\n");

return 0;
}

示例4:性能对比 - syscall vs 标准库函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <string.h>

// 性能测试宏
#define ITERATIONS 1000000

// 获取当前时间(微秒)
long get_time_usec() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000000 + tv.tv_usec;
}

int main() {
printf("=== syscall vs 标准库函数性能对比 ===\n");
printf("测试迭代次数: %d 次\n\n", ITERATIONS);

long start_time, end_time;
long syscall_time, library_time;

// 测试1: getpid系统调用性能
printf("1. getpid() 性能测试:\n");

// 使用syscall
start_time = get_time_usec();
for(int i = 0; i < ITERATIONS; i++) {
syscall(SYS_getpid);
}
end_time = get_time_usec();
syscall_time = end_time - start_time;

// 使用标准库函数
start_time = get_time_usec();
for(int i = 0; i < ITERATIONS; i++) {
getpid();
}
end_time = get_time_usec();
library_time = end_time - start_time;

printf(" syscall方式: %ld 微秒\n", syscall_time);
printf(" 标准库方式: %ld 微秒\n", library_time);
printf(" 性能差异: %ld 微秒 (%.2f%%)\n",
syscall_time - library_time,
((double)(syscall_time - library_time) / library_time) * 100);

// 测试2: getuid系统调用性能
printf("\n2. getuid() 性能测试:\n");

// 使用syscall
start_time = get_time_usec();
for(int i = 0; i < ITERATIONS; i++) {
syscall(SYS_getuid);
}
end_time = get_time_usec();
syscall_time = end_time - start_time;

// 使用标准库函数
start_time = get_time_usec();
for(int i = 0; i < ITERATIONS; i++) {
getuid();
}
end_time = get_time_usec();
library_time = end_time - start_time;

printf(" syscall方式: %ld 微秒\n", syscall_time);
printf(" 标准库方式: %ld 微秒\n", library_time);
printf(" 性能差异: %ld 微秒 (%.2f%%)\n",
syscall_time - library_time,
((double)(syscall_time - library_time) / library_time) * 100);

// 测试3: 文件操作性能对比
printf("\n3. 文件操作性能测试:\n");

const char* test_file = "perf_test.txt";
const char* test_data = "Performance test data\n";

// 使用syscall进行文件操作
start_time = get_time_usec();
for(int i = 0; i < 1000; i++) { // 减少迭代次数以避免文件系统压力
int fd = syscall(SYS_open, test_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if(fd != -1) {
syscall(SYS_write, fd, test_data, strlen(test_data));
syscall(SYS_close, fd);
}
}
end_time = get_time_usec();
long syscall_file_time = end_time - start_time;

// 使用标准库函数进行文件操作
start_time = get_time_usec();
for(int i = 0; i < 1000; i++) {
FILE* fp = fopen(test_file, "w");
if(fp != NULL) {
fwrite(test_data, 1, strlen(test_data), fp);
fclose(fp);
}
}
end_time = get_time_usec();
long library_file_time = end_time - start_time;

printf(" syscall方式: %ld 微秒\n", syscall_file_time);
printf(" 标准库方式: %ld 微秒\n", library_file_time);
printf(" 性能差异: %ld 微秒 (%.2f%%)\n",
syscall_file_time - library_file_time,
((double)(syscall_file_time - library_file_time) / library_file_time) * 100);

// 清理测试文件
unlink(test_file);

// 总结
printf("\n=== 性能测试总结 ===\n");
printf("1. 系统调用通常比标准库函数更快,因为避免了额外的封装开销\n");
printf("2. 但在某些情况下,标准库函数可能有优化(如缓存)\n");
printf("3. syscall更适合需要精确控制或访问新功能的场景\n");
printf("4. 标准库函数更适合日常开发,提供更好的可移植性和错误处理\n");

return 0;
}

编译和运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 编译示例1
gcc -o syscall_example1 syscall_example1.c
./syscall_example1

# 编译示例2
gcc -o syscall_example2 syscall_example2.c
./syscall_example2

# 编译示例3
gcc -o syscall_example3 syscall_example3.c
./syscall_example3

# 编译示例4
gcc -o syscall_example4 syscall_example4.c
./syscall_example4

重要注意事项

系统调用号: 不同架构的系统调用号可能不同,通常在/usr/include/asm/unistd.h中定义

错误处理: syscall失败时返回-1并设置errno,必须进行检查

参数类型: 参数类型必须与系统调用期望的类型匹配

可移植性: syscall是Linux特有的,不适用于其他操作系统

性能: syscall通常比标准库函数更快,但标准库函数更安全易用

权限: 某些系统调用需要特定权限才能执行

通过这些示例,你可以理解syscall作为系统调用接口的强大功能,它为底层系统编程提供了直接访问内核功能的能力。

data-ad-format="auto" data-full-width-responsive="true">