C语言作为一种底层编程语言,提供了与操作系统直接交互的能力。在Linux系统中,C语言通过系统调用(System Call)实现对系统资源的访问和控制。本文将详细讲解C语言在Linux系统中的系统调用操作,包括系统调用的基本概念、常见系统调用的使用方法、错误处理、系统调用与库函数的关系以及实际代码示例,帮助读者全面理解和掌握C语言与Linux系统的交互。
目录
- 系统调用概述
- 系统调用的实现机制
- C语言中的系统调用接口
- 常见的系统调用
- 文件操作系统调用
- 进程管理系统调用
- 内存管理系统调用
- 网络编程系统调用
- 系统调用与库函数
- 错误处理
- 直接使用系统调用
- 实际代码示例
- 文件操作示例
- 进程管理示例
- 内存管理示例
- 网络编程示例
- 总结
系统调用概述
什么是系统调用?
系统调用是用户空间(User Space)程序与内核空间(Kernel Space)之间的接口,用于请求操作系统内核提供的服务。通过系统调用,用户程序可以执行诸如文件操作、进程管理、内存分配、网络通信等任务。
为什么需要系统调用?
操作系统内核提供了受保护的资源和服务,用户程序无法直接访问这些资源。系统调用作为中介,确保了资源的安全和稳定,通过特定的接口允许用户程序安全地请求内核服务。
系统调用的特点
- 受保护性:系统调用需要在内核态执行,防止用户程序直接访问内核资源。
- 同步性:大多数系统调用是同步的,用户程序需要等待系统调用完成才能继续执行。
- 特权级别:系统调用在高特权级别(内核态)运行,用户程序在低特权级别(用户态)运行。
系统调用的实现机制
系统调用的实现涉及以下几个步骤:
- 用户空间触发:用户程序通过特定的函数(通常是库函数)触发系统调用。
- 中断或陷阱:触发一个软中断(如
int 0x80
)或使用syscall
指令,将控制权从用户态切换到内核态。 - 内核处理:内核根据系统调用号和参数执行相应的操作。
- 返回结果:内核将结果返回给用户空间,控制权切换回用户态。
系统调用号
每个系统调用都有一个唯一的系统调用号(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);
}
wait
和 waitpid
— 等待子进程结束
用途:使父进程等待子进程结束并回收其资源。
原型:
#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");
send
和 recv
— 发送和接收数据
用途:在已连接的套接字上发送和接收数据。
原型:
#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);
getuid
和 geteuid
— 获取用户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);
文件描述符操作系统调用
dup
和 dup2
— 复制文件描述符
用途:复制一个已有的文件描述符,返回一个新的文件描述符。
原型:
#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");
unlink
— 删除文件
用途:删除一个文件。
原型:
#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
的值,并使用 perror
或 strerror
函数打印错误信息。
示例
#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;
}
运行步骤:
编译服务器和客户端:
gcc -o server server.c gcc -o client client.c
启动服务器:
./server
在另一个终端启动客户端:
./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系统调用。