Administrator
Administrator
发布于 2024-12-06 / 7 阅读
0
0

C语言在Linux系统中的系统调用详解

C语言作为一种底层编程语言,提供了与操作系统直接交互的能力。在Linux系统中,C语言通过系统调用(System Call)实现对系统资源的访问和控制。本文将详细讲解C语言在Linux系统中的系统调用操作,包括系统调用的基本概念、常见系统调用的使用方法、错误处理、系统调用与库函数的关系以及实际代码示例,帮助读者全面理解和掌握C语言与Linux系统的交互。

目录

  1. 系统调用概述
  2. 系统调用的实现机制
  3. C语言中的系统调用接口
  4. 常见的系统调用
    • 文件操作系统调用
    • 进程管理系统调用
    • 内存管理系统调用
    • 网络编程系统调用
  5. 系统调用与库函数
  6. 错误处理
  7. 直接使用系统调用
  8. 实际代码示例
    • 文件操作示例
    • 进程管理示例
    • 内存管理示例
    • 网络编程示例
  9. 总结

系统调用概述

什么是系统调用?

系统调用是用户空间(User Space)程序与内核空间(Kernel Space)之间的接口,用于请求操作系统内核提供的服务。通过系统调用,用户程序可以执行诸如文件操作、进程管理、内存分配、网络通信等任务。

为什么需要系统调用?

操作系统内核提供了受保护的资源和服务,用户程序无法直接访问这些资源。系统调用作为中介,确保了资源的安全和稳定,通过特定的接口允许用户程序安全地请求内核服务。

系统调用的特点

  • 受保护性:系统调用需要在内核态执行,防止用户程序直接访问内核资源。
  • 同步性:大多数系统调用是同步的,用户程序需要等待系统调用完成才能继续执行。
  • 特权级别:系统调用在高特权级别(内核态)运行,用户程序在低特权级别(用户态)运行。

系统调用的实现机制

系统调用的实现涉及以下几个步骤:

  1. 用户空间触发:用户程序通过特定的函数(通常是库函数)触发系统调用。
  2. 中断或陷阱:触发一个软中断(如 int 0x80)或使用 syscall 指令,将控制权从用户态切换到内核态。
  3. 内核处理:内核根据系统调用号和参数执行相应的操作。
  4. 返回结果:内核将结果返回给用户空间,控制权切换回用户态。

系统调用号

每个系统调用都有一个唯一的系统调用号(System Call Number),用于在内核中识别具体的系统调用。例如,open 系统调用在不同的架构中可能有不同的系统调用号。

参数传递

系统调用的参数通常通过寄存器传递。例如,在x86_64架构中,系统调用号通常存放在 rax 寄存器,参数依次存放在 rdi, rsi, rdx, r10, r8, r9 寄存器中。


C语言中的系统调用接口

在C语言中,系统调用通常通过标准库函数(如 printf, open, read 等)间接调用内核的系统调用接口。这些库函数作为系统调用的包装器,提供了更高层次的抽象,简化了系统调用的使用。

头文件

大多数系统调用函数定义在以下头文件中:

  • unistd.h:包含许多POSIX标准的系统调用函数,如 read, write, fork 等。
  • sys/types.h:定义数据类型,如 pid_t, size_t 等。
  • sys/stat.h:用于文件状态信息,如 stat 系统调用。
  • fcntl.h:文件控制选项,如 open 系统调用。
  • sys/socket.h:套接字编程相关的系统调用。

系统调用函数原型示例

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// read系统调用
ssize_t read(int fd, void *buf, size_t count);

// write系统调用
ssize_t write(int fd, const void *buf, size_t count);

// open系统调用
int open(const char *pathname, int flags, mode_t mode);

// close系统调用
int close(int fd);

这些函数通过C标准库(如glibc)实现,与内核的系统调用接口进行通信。


常见的系统调用

以下将详细介绍C语言中常用的Linux系统调用,包括文件操作、进程管理、内存管理和网络编程等方面。

文件操作系统调用

open — 打开文件

用途:打开或创建一个文件,返回文件描述符。

原型

#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);

参数

  • pathname:文件路径。
  • flags:打开方式,如 O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC 等。
  • mode:文件权限,仅在创建文件时有效,如 0644

示例

int fd = open("example.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
    perror("open");
    exit(EXIT_FAILURE);
}

read — 读取文件

用途:从文件描述符中读取数据。

原型

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

参数

  • fd:文件描述符。
  • buf:存储读取数据的缓冲区。
  • count:要读取的字节数。

示例

char buffer[100];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
if (bytesRead == -1) {
    perror("read");
    close(fd);
    exit(EXIT_FAILURE);
}

write — 写入文件

用途:向文件描述符中写入数据。

原型

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

参数

  • fd:文件描述符。
  • buf:要写入的数据缓冲区。
  • count:要写入的字节数。

示例

const char *text = "Hello, World!\n";
ssize_t bytesWritten = write(fd, text, strlen(text));
if (bytesWritten == -1) {
    perror("write");
    close(fd);
    exit(EXIT_FAILURE);
}

close — 关闭文件

用途:关闭打开的文件描述符。

原型

#include <unistd.h>
int close(int fd);

参数

  • fd:文件描述符。

示例

if (close(fd) == -1) {
    perror("close");
    exit(EXIT_FAILURE);
}

lseek — 调整文件偏移量

用途:移动文件描述符的读写位置。

原型

#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

参数

  • fd:文件描述符。
  • offset:偏移量。
  • whence:参考位置,如 SEEK_SET, SEEK_CUR, SEEK_END

示例

off_t newPos = lseek(fd, 0, SEEK_END);
if (newPos == -1) {
    perror("lseek");
    close(fd);
    exit(EXIT_FAILURE);
}

进程管理系统调用

fork — 创建子进程

用途:创建一个与当前进程相同的子进程。

原型

#include <unistd.h>
pid_t fork(void);

返回值

  • 在父进程中返回子进程的PID。
  • 在子进程中返回0。
  • 出错时返回-1。

示例

pid_t pid = fork();
if (pid == -1) {
    perror("fork");
    exit(EXIT_FAILURE);
} else if (pid == 0) {
    // 子进程
    printf("这是子进程,PID=%d\n", getpid());
    exit(EXIT_SUCCESS);
} else {
    // 父进程
    printf("这是父进程,子进程PID=%d\n", pid);
}

exec 系列 — 执行新程序

用途:在当前进程空间中执行新程序,替换当前进程的映像。

常用函数

  • execl
  • execv
  • execle
  • execve
  • execlp
  • execvp

示例(使用 execlp 执行 ls 命令):

#include <unistd.h>
#include <stdlib.h>

pid_t pid = fork();
if (pid == -1) {
    perror("fork");
    exit(EXIT_FAILURE);
} else if (pid == 0) {
    // 子进程执行 ls 命令
    execlp("ls", "ls", "-l", (char *)NULL);
    // 如果 exec 成功,下面的代码不会执行
    perror("execlp");
    exit(EXIT_FAILURE);
} else {
    // 父进程等待子进程结束
    wait(NULL);
}

waitwaitpid — 等待子进程结束

用途:使父进程等待子进程结束并回收其资源。

原型

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

示例

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

pid_t pid = fork();
if (pid == -1) {
    perror("fork");
    exit(EXIT_FAILURE);
} else if (pid == 0) {
    // 子进程
    sleep(2);
    exit(EXIT_SUCCESS);
} else {
    // 父进程等待子进程
    int status;
    pid_t wpid = wait(&status);
    if (wpid == -1) {
        perror("wait");
        exit(EXIT_FAILURE);
    }
    if (WIFEXITED(status)) {
        printf("子进程 %d 正常退出,状态码 %d\n", wpid, WEXITSTATUS(status));
    } else {
        printf("子进程 %d 异常退出\n", wpid);
    }
}

内存管理系统调用

mmap — 内存映射

用途:将文件或设备映射到内存地址空间,实现高效的文件I/O。

原型

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

参数

  • addr:建议的映射地址,通常设为 NULL
  • length:映射区域的长度。
  • prot:内存保护标志,如 PROT_READ, PROT_WRITE
  • flags:映射选项,如 MAP_PRIVATE, MAP_SHARED
  • fd:文件描述符。
  • offset:文件偏移量。

示例

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    size_t length = 100;
    char *map = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        perror("mmap");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 使用映射内存
    for (size_t i = 0; i < length && map[i] != '\0'; i++) {
        putchar(map[i]);
    }

    // 解除映射
    if (munmap(map, length) == -1) {
        perror("munmap");
    }

    close(fd);
    return 0;
}

munmap — 解除内存映射

用途:解除之前通过 mmap 创建的内存映射。

原型

#include <sys/mman.h>
int munmap(void *addr, size_t length);

示例:见上文 mmap 示例。

网络编程系统调用

socket — 创建套接字

用途:创建一个套接字,用于网络通信。

原型

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

参数

  • domain:协议族,如 AF_INET(IPv4), AF_INET6(IPv6)。
  • type:套接字类型,如 SOCK_STREAM(TCP), SOCK_DGRAM(UDP)。
  • protocol:协议,通常设为0,由系统自动选择。

示例

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }
    printf("套接字创建成功,fd=%d\n", sockfd);
    close(sockfd);
    return 0;
}

bind — 绑定套接字到地址

用途:将套接字绑定到特定的IP地址和端口。

原型

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

示例

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080); // 端口号
    addr.sin_addr.s_addr = INADDR_ANY; // 监听所有接口

    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("bind");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("套接字绑定成功\n");
    close(sockfd);
    return 0;
}

listen — 监听套接字

用途:将套接字设置为被动模式,监听传入连接。

原型

#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);

参数

  • sockfd:套接字描述符。
  • backlog:等待连接队列的长度。

示例

if (listen(sockfd, 10) == -1) {
    perror("listen");
    close(sockfd);
    exit(EXIT_FAILURE);
}
printf("套接字正在监听...\n");

accept — 接受连接

用途:接受一个传入的连接,返回一个新的套接字描述符。

原型

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

示例

struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd == -1) {
    perror("accept");
    close(sockfd);
    exit(EXIT_FAILURE);
}
printf("接受到连接,客户端fd=%d\n", client_fd);
close(client_fd);

connect — 连接到远程地址

用途:主动连接到一个远程套接字。

原型

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

示例

struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
    perror("connect");
    close(sockfd);
    exit(EXIT_FAILURE);
}
printf("连接到服务器成功\n");

sendrecv — 发送和接收数据

用途:在已连接的套接字上发送和接收数据。

原型

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

示例

char *message = "Hello, Server!";
if (send(sockfd, message, strlen(message), 0) == -1) {
    perror("send");
    close(sockfd);
    exit(EXIT_FAILURE);
}
printf("发送消息成功\n");

char buffer[1024];
ssize_t bytesReceived = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
if (bytesReceived == -1) {
    perror("recv");
    close(sockfd);
    exit(EXIT_FAILURE);
}
buffer[bytesReceived] = '\0';
printf("接收到消息: %s\n", buffer);

内核相关系统调用

getpid — 获取进程ID

用途:获取当前进程的进程ID。

原型

#include <unistd.h>
pid_t getpid(void);

示例

pid_t pid = getpid();
printf("当前进程ID: %d\n", pid);

getppid — 获取父进程ID

用途:获取当前进程的父进程ID。

原型

#include <unistd.h>
pid_t getppid(void);

示例

pid_t ppid = getppid();
printf("父进程ID: %d\n", ppid);

getuidgeteuid — 获取用户ID

用途:获取真实用户ID和有效用户ID。

原型

#include <unistd.h>
uid_t getuid(void);
uid_t geteuid(void);

示例

uid_t uid = getuid();
uid_t euid = geteuid();
printf("真实用户ID: %d, 有效用户ID: %d\n", uid, euid);

setuid — 设置用户ID

用途:设置进程的用户ID。

原型

#include <unistd.h>
int setuid(uid_t uid);

示例

if (setuid(1000) == -1) {
    perror("setuid");
    exit(EXIT_FAILURE);
}
printf("用户ID已设置为1000\n");

exit — 终止进程

用途:终止当前进程,并返回状态码。

原型

#include <stdlib.h>
void exit(int status);

示例

exit(EXIT_SUCCESS);

文件描述符操作系统调用

dupdup2 — 复制文件描述符

用途:复制一个已有的文件描述符,返回一个新的文件描述符。

原型

#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);

示例

int newfd = dup(sockfd);
if (newfd == -1) {
    perror("dup");
    close(sockfd);
    exit(EXIT_FAILURE);
}
printf("复制的文件描述符: %d\n", newfd);

信号处理系统调用

signal — 设置信号处理器

用途:设置特定信号的处理函数。

原型

#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);

示例

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void handle_sigint(int sig) {
    printf("接收到SIGINT信号,退出程序\n");
    exit(EXIT_SUCCESS);
}

int main() {
    if (signal(SIGINT, handle_sigint) == SIG_ERR) {
        perror("signal");
        exit(EXIT_FAILURE);
    }

    while (1) {
        printf("程序运行中,按Ctrl+C退出\n");
        sleep(1);
    }

    return 0;
}

kill — 发送信号

用途:向指定进程发送信号。

原型

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

示例

pid_t pid = 1234; // 目标进程ID
if (kill(pid, SIGTERM) == -1) {
    perror("kill");
    exit(EXIT_FAILURE);
}
printf("已发送SIGTERM信号给进程 %d\n", pid);

其他常见系统调用

chmod — 修改文件权限

用途:修改文件或目录的权限。

原型

#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);

示例

if (chmod("example.txt", 0644) == -1) {
    perror("chmod");
    exit(EXIT_FAILURE);
}
printf("文件权限已修改为0644\n");

chown — 修改文件所有者

用途:修改文件或目录的所有者和所属组。

原型

#include <sys/types.h>
#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);

示例

if (chown("example.txt", 1000, 1000) == -1) {
    perror("chown");
    exit(EXIT_FAILURE);
}
printf("文件所有者和所属组已修改为1000\n");

getcwd — 获取当前工作目录

用途:获取进程的当前工作目录。

原型

#include <unistd.h>
char *getcwd(char *buf, size_t size);

示例

char cwd[1024];
if (getcwd(cwd, sizeof(cwd)) == NULL) {
    perror("getcwd");
    exit(EXIT_FAILURE);
}
printf("当前工作目录: %s\n", cwd);

chdir — 改变当前工作目录

用途:改变进程的当前工作目录。

原型

#include <unistd.h>
int chdir(const char *path);

示例

if (chdir("/tmp") == -1) {
    perror("chdir");
    exit(EXIT_FAILURE);
}
printf("工作目录已切换到 /tmp\n");

mkdir — 创建目录

用途:创建一个新目录。

原型

#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);

示例

if (mkdir("new_directory", 0755) == -1) {
    perror("mkdir");
    exit(EXIT_FAILURE);
}
printf("目录 'new_directory' 创建成功\n");

rmdir — 删除目录

用途:删除一个空目录。

原型

#include <unistd.h>
int rmdir(const char *pathname);

示例

if (rmdir("new_directory") == -1) {
    perror("rmdir");
    exit(EXIT_FAILURE);
}
printf("目录 'new_directory' 删除成功\n");

用途:删除一个文件。

原型

#include <unistd.h>
int unlink(const char *pathname);

示例

if (unlink("example.txt") == -1) {
    perror("unlink");
    exit(EXIT_FAILURE);
}
printf("文件 'example.txt' 删除成功\n");

rename — 重命名文件

用途:重命名一个文件或目录。

原型

#include <unistd.h>
int rename(const char *oldpath, const char *newpath);

示例

if (rename("oldname.txt", "newname.txt") == -1) {
    perror("rename");
    exit(EXIT_FAILURE);
}
printf("文件重命名成功\n");

stat — 获取文件状态

用途:获取文件或目录的状态信息,如大小、权限、修改时间等。

原型

#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);

示例

struct stat fileStat;
if (stat("example.txt", &fileStat) == -1) {
    perror("stat");
    exit(EXIT_FAILURE);
}
printf("文件大小: %ld bytes\n", fileStat.st_size);
printf("权限: %o\n", fileStat.st_mode & 0777);

系统调用与库函数

系统调用与标准库函数的关系

在C语言中,系统调用通常通过标准库函数(如glibc)进行封装。标准库函数提供了更高层次的抽象,简化了系统调用的使用,并处理了一些复杂的细节,如缓冲、错误处理等。

例如,printf 函数最终会调用 write 系统调用将数据输出到标准输出。fopen 函数会调用 open 系统调用打开文件,并返回一个 FILE 结构体指针,提供更便捷的文件操作接口。

直接使用系统调用

虽然大多数情况下使用标准库函数足够,但在某些需要更高性能或更低级别控制的场景下,可以直接使用系统调用。直接使用系统调用需要理解系统调用号和参数传递机制,通常通过内联汇编或特定的库函数(如 syscall 函数)实现。

示例(使用 syscall 函数直接调用 getpid 系统调用):

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>

int main() {
    pid_t pid = syscall(SYS_getpid);
    printf("当前进程ID: %d\n", pid);
    return 0;
}

注意:直接使用系统调用可能导致可移植性问题,因为系统调用号和行为可能在不同的内核版本或架构中有所不同。因此,除非必要,推荐使用标准库函数。


错误处理

系统调用在执行过程中可能会失败,返回错误码。错误处理是系统调用使用中的重要环节,确保程序的健壮性和正确性。

错误返回值

  • 成功:系统调用成功时,返回一个非负值,具体含义取决于系统调用。
  • 失败:系统调用失败时,返回-1,并设置 errno 变量以指示错误原因。

errno 变量

errno 是一个全局变量,用于保存最近一次系统调用失败的错误码。通过包含 <errno.h> 头文件,可以访问 errno 的值,并使用 perrorstrerror 函数打印错误信息。

示例

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main() {
    // 尝试打开一个不存在的文件
    int fd = open("nonexistent.txt", O_RDONLY);
    if (fd == -1) {
        // 打印错误信息
        perror("open");
        // 或者使用 strerror
        // fprintf(stderr, "Error opening file: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    close(fd);
    return 0;
}

输出示例

open: No such file or directory

常见的 errno

  • EACCES:权限被拒绝。
  • EEXIST:文件已存在。
  • EINVAL:无效参数。
  • ENOMEM:内存不足。
  • EINTR:被信号中断。
  • EBADF:错误的文件描述符。

更多错误码及其含义可参考 <errno.h> 头文件或相关文档。


直接使用系统调用

虽然标准库函数提供了便捷的接口,但有时需要直接使用系统调用以获得更高的性能或更低级别的控制。Linux提供了 syscall 函数用于直接调用系统调用。

syscall 函数

原型

#include <unistd.h>
#include <sys/syscall.h>
long syscall(long number, ...);

参数

  • number:系统调用号,如 SYS_write, SYS_read 等。
  • 其他参数:根据系统调用的需求传递。

示例(直接使用 syscall 调用 write 系统调用):

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

int main() {
    const char *msg = "Hello via syscall!\n";
    syscall(SYS_write, STDOUT_FILENO, msg, 19);
    return 0;
}

注意

  • 使用 syscall 需要了解系统调用号和参数。
  • 不推荐在普通应用程序中频繁使用,除非有特殊需求。
  • 可移植性较差,因为系统调用号可能因内核版本或架构而异。

获取系统调用号

系统调用号可以通过查阅系统头文件(如 /usr/include/x86_64-linux-gnu/asm/unistd_64.h)或通过在线资源获取。例如,SYS_write 的系统调用号在x86_64架构中通常为1。


实际代码示例

以下通过几个实际的代码示例,展示如何在C语言中使用系统调用实现文件操作、进程管理、内存管理和网络编程。

文件操作示例

创建一个新文件,写入内容并读取内容。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    // 打开或创建文件
    int fd = open("example.txt", O_CREAT | O_RDWR, 0644);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 写入数据
    const char *text = "Hello, Linux System Calls!\n";
    ssize_t bytesWritten = write(fd, text, strlen(text));
    if (bytesWritten == -1) {
        perror("write");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("写入了 %ld 字节\n", bytesWritten);

    // 设置文件偏移量到文件开头
    if (lseek(fd, 0, SEEK_SET) == -1) {
        perror("lseek");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 读取数据
    char buffer[100];
    ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);
    if (bytesRead == -1) {
        perror("read");
        close(fd);
        exit(EXIT_FAILURE);
    }
    buffer[bytesRead] = '\0';
    printf("读取内容: %s", buffer);

    // 关闭文件
    if (close(fd) == -1) {
        perror("close");
        exit(EXIT_FAILURE);
    }

    return 0;
}

输出示例

写入了 27 字节
读取内容: Hello, Linux System Calls!

进程管理示例

创建一个子进程,子进程执行新程序,父进程等待子进程结束。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程执行新程序
        execlp("ls", "ls", "-l", (char *)NULL);
        // 如果execlp失败
        perror("execlp");
        exit(EXIT_FAILURE);
    } else {
        // 父进程等待子进程结束
        int status;
        if (waitpid(pid, &status, 0) == -1) {
            perror("waitpid");
            exit(EXIT_FAILURE);
        }
        if (WIFEXITED(status)) {
            printf("子进程退出,状态码 %d\n", WEXITSTATUS(status));
        } else {
            printf("子进程异常退出\n");
        }
    }
    return 0;
}

输出示例(部分):

total 8
-rw-r--r-- 1 user user   27 Apr 27 10:00 example.txt
-rwxr-xr-x 1 user user  194 Apr 27 10:05 process_example
子进程退出,状态码 0

内存管理示例

使用 mmap 创建内存映射,写入和读取数据。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>

int main() {
    const char *filename = "mmap_example.txt";
    int fd = open(filename, O_CREAT | O_RDWR, 0644);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 扩展文件大小
    if (ftruncate(fd, 100) == -1) {
        perror("ftruncate");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 创建内存映射
    char *map = mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (map == MAP_FAILED) {
        perror("mmap");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 写入数据
    const char *text = "Hello via mmap!";
    strncpy(map, text, strlen(text));

    // 同步内存映射到文件
    if (msync(map, 100, MS_SYNC) == -1) {
        perror("msync");
    }

    // 读取数据
    printf("映射内容: %s\n", map);

    // 解除映射
    if (munmap(map, 100) == -1) {
        perror("munmap");
    }

    close(fd);
    return 0;
}

输出示例

映射内容: Hello via mmap!

生成的文件内容mmap_example.txt):

Hello via mmap!...

(后续内容为空白或填充内容)

网络编程示例

创建一个简单的TCP服务器,接受连接并发送欢迎消息。

服务器代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address;
    socklen_t addrlen = sizeof(address);
    char *welcome = "Welcome to the server!\n";

    // 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置地址
    memset(&address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY; // 监听所有接口
    address.sin_port = htons(PORT);

    // 绑定套接字
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) == -1) {
        perror("bind");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 监听套接字
    if (listen(server_fd, 3) == -1) {
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("服务器正在监听端口 %d...\n", PORT);

    // 接受连接
    if ((client_fd = accept(server_fd, (struct sockaddr *)&address, &addrlen)) == -1) {
        perror("accept");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("接受到一个连接\n");

    // 发送欢迎消息
    if (write(client_fd, welcome, strlen(welcome)) == -1) {
        perror("write");
    }

    // 关闭套接字
    close(client_fd);
    close(server_fd);
    return 0;
}

客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    char buffer[1024] = {0};

    // 创建套接字
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);

    // 转换IP地址
    if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {
        perror("inet_pton");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 连接服务器
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    printf("已连接到服务器\n");

    // 接收欢迎消息
    ssize_t bytesRead = read(sockfd, buffer, sizeof(buffer) - 1);
    if (bytesRead == -1) {
        perror("read");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    buffer[bytesRead] = '\0';
    printf("接收到消息: %s", buffer);

    // 关闭套接字
    close(sockfd);
    return 0;
}

运行步骤

  1. 编译服务器和客户端:

    gcc -o server server.c
    gcc -o client client.c
    
  2. 启动服务器:

    ./server
    
  3. 在另一个终端启动客户端:

    ./client
    

输出示例

服务器端

服务器正在监听端口 8080...
接受到一个连接

客户端

已连接到服务器
接收到消息: Welcome to the server!

总结

本文详细介绍了C语言在Linux系统中的系统调用操作,包括系统调用的基本概念、实现机制、C语言接口、常见系统调用的使用方法以及系统调用与库函数的关系。通过实际代码示例,展示了如何在C程序中使用系统调用实现文件操作、进程管理、内存管理和网络编程等功能。

掌握系统调用是深入理解操作系统和高效编程的关键。建议读者在实际项目中多加练习,结合Linux系统的文档(如 man 页面)和源码,进一步探索系统调用的高级用法和优化技巧。同时,理解系统调用的底层机制有助于编写更高效、稳定和安全的应用程序。


附录:常见系统调用快速参考

系统调用 描述 头文件
open 打开或创建文件 <fcntl.h>
read 从文件描述符中读取数据 <unistd.h>
write 向文件描述符中写入数据 <unistd.h>
close 关闭文件描述符 <unistd.h>
fork 创建子进程 <unistd.h>
exec 执行新程序 <unistd.h>
wait 等待子进程结束 <sys/wait.h>
mmap 创建内存映射 <sys/mman.h>
munmap 解除内存映射 <sys/mman.h>
socket 创建套接字 <sys/socket.h>
bind 绑定套接字到地址 <sys/socket.h>
listen 监听套接字 <sys/socket.h>
accept 接受连接 <sys/socket.h>
connect 连接到远程地址 <sys/socket.h>
send 发送数据 <sys/socket.h>
recv 接收数据 <sys/socket.h>
getpid 获取进程ID <unistd.h>
getppid 获取父进程ID <unistd.h>
getuid 获取真实用户ID <unistd.h>
geteuid 获取有效用户ID <unistd.h>
setuid 设置用户ID <unistd.h>
chmod 修改文件权限 <sys/stat.h>
chown 修改文件所有者 <unistd.h>
getcwd 获取当前工作目录 <unistd.h>
chdir 改变当前工作目录 <unistd.h>
mkdir 创建目录 <sys/stat.h>
rmdir 删除空目录 <unistd.h>
unlink 删除文件 <unistd.h>
rename 重命名文件或目录 <unistd.h>
stat 获取文件状态信息 <sys/stat.h>
signal 设置信号处理器 <signal.h>
kill 发送信号 <sys/types.h>
syscall 直接调用系统调用 <unistd.h>

通过参考上述快速参考表,您可以更方便地查找和使用C语言中的Linux系统调用。


评论