syscall系统调用及示例

syscall函数详解

1. 函数介绍

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

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

使用场景:

  • 访问新的或非标准的系统调用
  • 学习和理解系统调用机制
  • 实现系统级编程和调试工具
  • 调用特定架构的系统调用
  • 性能敏感的应用程序直接调用内核功能

2. 函数原型

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

long syscall(long number, ...);

3. 功能

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

4. 参数

  • number: 系统调用号
    • 类型:long
    • 含义:要调用的系统调用的唯一标识号,每个系统调用都有一个固定的编号
  • : 可变参数
    • 类型:可变参数列表
    • 含义:传递给系统调用的参数,参数的数量和类型取决于具体的系统调用

5. 返回值

  • 成功: 返回系统调用的返回值(通常是long类型)
  • 失败: 返回-1,并设置errno错误码
    • 各种错误码取决于具体的系统调用和错误情况

6. 相似函数或关联函数

  • 系统调用封装函数: 如open(), read(), write(), fork(), exec()等
  • syscall()的特定封装: 如tgkill(), gettid()等
  • 内联汇编: 直接使用汇编指令调用系统调用
  • ioctl(): 设备控制的系统调用
  • 系统调用表: /usr/include/asm/unistd.h中定义的系统调用号

7. 示例代码

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

#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[256];
        ssize_t bytes_read = syscall(SYS_read, fd, buffer, sizeof(buffer) - 1);
        if(bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("读取内容: %s", buffer);
        }
        syscall(SYS_close, fd);
    }
    
    // 清理测试文件
    syscall(SYS_unlink, filename);
    
    return 0;
}

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

#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[65];
        char nodename[65];
        char release[65];
        char version[65];
        char machine[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:自定义系统调用和错误处理

#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[] = {
        {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[0]);
    
    printf("\n系统信息获取测试:\n");
    printf("----------------------------\n");
    
    for(int i = 0; i < num_syscalls; i++) {
        printf("%-10s: ", syscalls[i].description);
        long result = safe_syscall(syscalls[i].number, syscalls[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[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 标准库函数

#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
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

重要注意事项

  1. 系统调用号: 不同架构的系统调用号可能不同,通常在/usr/include/asm/unistd.h中定义
  2. 错误处理: syscall失败时返回-1并设置errno,必须进行检查
  3. 参数类型: 参数类型必须与系统调用期望的类型匹配
  4. 可移植性: syscall是Linux特有的,不适用于其他操作系统
  5. 性能: syscall通常比标准库函数更快,但标准库函数更安全易用
  6. 权限: 某些系统调用需要特定权限才能执行

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

此条目发表在linux文章分类目录,贴了标签。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注