什么是系统调用 系统调用是操作系统提供对外提供服务的方式,应用程序通过系统调用请求操作系统完成特定功能
在C语言中,系统调用表现为一系列函数的调用,常见的系统调用函数如下
进程控制
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 #include <stdio.h> #include <unistd.h> #include <sys/types.h> int main () { pid_t pid; pid = fork(); if (pid < 0 ) { perror("fork failed" ); return 1 ; } else if (pid == 0 ) { printf ("This is the child process. PID: %d\n" , getpid()); printf ("Child process ID: %d\n" , getpid()); } else { printf ("This is the parent process. PID: %d\n" , getpid()); printf ("Parent process ID: %d\n" , getpid()); printf ("Created child process with PID: %d\n" , pid); } return 0 ; }
fork系统调用能让程序拥有多个执行流,提供了在程序中实现多任务的能力
fork后的父进程和子进程虽然拥有独立的内存空间(包括堆内存,使用写时复制(COW)技术实现),但它们共享某些操作系统级别的资源,如底层文件描述符、网络套接字,例如如下情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0 ) { perror("accept" ); exit (EXIT_FAILURE); } pid_t pid = fork();if (pid == 0 ) { close(server_fd); handle_client(new_socket); close(new_socket); exit (0 ); } else if (pid > 0 ) { close(new_socket); }
fork后,socket本身并没有被克隆,只是它的引用被复制了,所以为避免互相干扰,各进程关闭多余的socket引用复制,只留自身需要的引用,避免出现并发安全问题。
**exec()**:执行一个程序(有多个变种,如execl()、execp()等)。
子程序
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main (int argc, char **argv) { printf ("Hello from the child program!\n" ); printf ("Received parameters: " ); for (int i=0 ;i<argc;i++){ printf ("%s " ,argv[i]); } printf ("\n" ); return 0 ; }
父程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> #include <unistd.h> int main () { printf ("This is the parent program.\n" ); printf ("Now executing the child program...\n" ); execl("./child" , "child" , NULL ); printf ("If you see this message, exec failed!\n" ); return 0 ; }
execl() : 这是exec系列调用的一种,参数包括程序路径、参数列表,参数列表以NULL结尾。
"./child" : 指定要执行的可执行文件(child)。
"child" : 传递给子程序的第一个参数,通常是程序名(argv[0])。
Shell(如bash、zsh等)使用exec系统调用来执行用户输入的命令。例如,当你在命令行中输入一个命令时,Shell解析命令后,会使用fork()创建一个子进程,然后在子进程中调用exec来执行该命令对应的程序。exec会将当前进程的内容替换为新程序,而不会创建新的进程,这节省了系统资源。
**exit()**:终止进程并返回状态码。
**wait()**:等待子进程结束。
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main () { pid_t pid = fork(); if (pid < 0 ) { perror("Fork failed" ); return 1 ; } else if (pid == 0 ) { printf ("Child process (PID: %d) is running...\n" , getpid()); sleep(2 ); printf ("Child process (PID: %d) is exiting...\n" , getpid()); exit (42 ); } else { int status; printf ("Parent process (PID: %d) is waiting for child process (PID: %d) to finish...\n" , getpid(), pid); wait(&status); if (WIFEXITED(status)) { printf ("Child process terminated normally with exit status %d\n" , WEXITSTATUS(status)); } else { printf ("Child process terminated abnormally\n" ); } } return 0 ; }
应用场景:一个父进程创建多个子进程,每个子进程负责处理一个独立的任务。父进程使用wait()逐一等待所有子进程完成后,再收集结果或继续执行后续任务
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main () { pid_t pids[3 ]; int i; for (i = 0 ; i < 3 ; i++) { pids[i] = fork(); if (pids[i] < 0 ) { perror("Fork failed" ); return 1 ; } else if (pids[i] == 0 ) { printf ("Child %d (PID: %d) is running task...\n" , i + 1 , getpid()); sleep(2 + i); printf ("Child %d (PID: %d) is exiting...\n" , i + 1 , getpid()); exit (10 + i); } } for (i = 0 ; i < 3 ; i++) { int status; pid_t pid = wait(&status); if (pid > 0 ) { printf ("Parent process collected child process (PID: %d)\n" , pid); if (WIFEXITED(status)) { printf ("Child process (PID: %d) terminated with exit status %d\n" , pid, WEXITSTATUS(status)); } else { printf ("Child process (PID: %d) terminated abnormally\n" , pid); } } else { perror("Wait failed" ); } } printf ("All child processes have finished. Parent process (PID: %d) is exiting...\n" , getpid()); return 0 ; }
wait()系统调用会使父进程阻塞,直到它的任意一个子进程终止。wait()返回的值是结束的子进程的进程ID
**getpid()**:获取当前进程ID。
**getppid()**:获取父进程ID。
**kill()**:向进程发送信号。
文件操作
**open()**:打开文件。
**read()**:从文件中读取数据。
函数原型
1 ssize_t read (int fd, void *buf, size_t count) ;
参数
**fd**:要读取的文件描述符。这个文件描述符通常是由 open 函数返回的,或者是标准输入、输出和错误流(如 stdin、stdout、stderr)的文件描述符。
**buf**:指向一个内存缓冲区的指针,用于存储读取的数据。
**count**:要读取的最大字节数。
返回值
成功 :返回实际读取的字节数。如果返回值小于 count,则表示读取的字节数少于请求的字节数,可能是因为到达了文件末尾或缓冲区已满。
失败 :返回 -1,并设置 errno 以指示错误类型。常见的错误包括 EBADF(文件描述符无效)、EFAULT(缓冲区指针无效)和 EINTR(操作被中断)。
例子:从标准输入读取数据
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 #include <stdio.h> #include <unistd.h> #define BUF_SIZE 3 int main () { char buffer[BUF_SIZE]; ssize_t bytesRead; while (1 ){ bytesRead = read(STDIN_FILENO, buffer, sizeof (buffer) - 1 ); if (bytesRead < 0 ) { perror("read" ); return 1 ; } buffer[bytesRead] = '\0' ; printf ("Read %zd bytes: %s\n" , bytesRead, buffer); } return 0 ; }
read函数会阻塞直到有可获得的数据
**write()**:向文件中写入数据。
**close()**:关闭文件描述符。
**lseek()**:移动文件指针。
**unlink()**:删除文件。
**stat()**:获取文件状态信息。
**fstat()**:获取文件描述符的状态信息。
**lstat()**:获取文件状态信息(处理符号链接)。
文件输出
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 #include <stdio.h> #include <fcntl.h> #include <unistd.h> int main () { int fd; char *filename = "example.txt" ; char write_buf[] = "Hello, Linux system calls!" ; ssize_t bytes_written; fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644 ); if (fd < 0 ) { perror("open for write" ); return 1 ; } bytes_written = write(fd, write_buf, sizeof (write_buf)); if (bytes_written < 0 ) { perror("write" ); close(fd); return 1 ; } printf ("Data written to %s\n" , filename); close(fd); return 0 ; }
文件输入
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 #include <stdio.h> #include <fcntl.h> #include <unistd.h> int main () { int fd; char *filename = "example.txt" ; char read_buf[256 ]; ssize_t bytes_read; fd = open(filename, O_RDONLY); if (fd < 0 ) { perror("open for read" ); return 1 ; } bytes_read = read(fd, read_buf, sizeof (read_buf) - 1 ); if (bytes_read < 0 ) { perror("read" ); close(fd); return 1 ; } read_buf[bytes_read] = '\0' ; printf ("Read from file: %s\n" , read_buf); close(fd); return 0 ; }
3. 内存管理
**mmap()**:映射文件到内存。
**munmap()**:解除映射。
**brk()**:调整数据段大小(用于堆)。
**sbrk()**:增加或减少堆的大小。
4. 设备操作
ioctl是input output control的缩写,是一个非常强大的系统调用函数,能做很多需要root权限的操作
例子:获取网络接口的ip地址
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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <net/if.h> #include <arpa/inet.h> #include <unistd.h> int main () { int sockfd; struct ifreq ifr ; struct sockaddr_in * ipaddr ; sockfd = socket(AF_INET, SOCK_DGRAM, 0 ); if (sockfd < 0 ) { perror("socket" ); exit (EXIT_FAILURE); } strncpy (ifr.ifr_name, "eth0" , IFNAMSIZ - 1 ); ifr.ifr_addr.sa_family = AF_INET; if (ioctl(sockfd, SIOCGIFADDR, &ifr) < 0 ) { perror("ioctl" ); close(sockfd); exit (EXIT_FAILURE); } ipaddr = (struct sockaddr_in *)&ifr.ifr_addr; printf ("IP Address: %s\n" , inet_ntoa(ipaddr->sin_addr)); close(sockfd); return 0 ; }
网络接口相关的 ioctl 命令
**SIOCGIFNAME**:获取接口名称。
**SIOCGIFBRDADDR**:获取广播地址。
**SIOCGIFNETMASK**:获取网络掩码。
**SIOCGIFHWADDR**:获取硬件地址(MAC 地址)。
**SIOCGIFFLAGS**:获取接口标志(如是否启用)。
**SIOCSIFFLAGS**:设置接口标志(如启用或禁用接口)。
**SIOCGIFMTU**:获取接口的最大传输单元(MTU)。
**SIOCSIFMTU**:设置接口的最大传输单元(MTU)。
重磅级ioctl命令**TUNSETIFF**(TUN SET Interface Flags)
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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <net/if.h> #include <unistd.h> #include <fcntl.h> #include <linux/if_tun.h> int main () { int fd; struct ifreq ifr ; char *dev = "/dev/net/tun" ; fd = open(dev, O_RDWR); if (fd < 0 ) { perror("open" ); exit (EXIT_FAILURE); } memset (&ifr, 0 , sizeof (ifr)); ifr.ifr_flags = IFF_TUN | IFF_NO_PI; strncpy (ifr.ifr_name, "tun0" , IFNAMSIZ); if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0 ) { perror("ioctl TUNSETIFF" ); close(fd); exit (EXIT_FAILURE); } printf ("Created TUN device: %s\n" , ifr.ifr_name); sleep(10 ); close(fd); return 0 ; }
5. 网络通信
**socket()**:创建一个套接字。
**bind()**:绑定套接字到一个地址。
**listen()**:监听套接字上的连接请求。
**accept()**:接受连接请求。
**connect()**:连接到一个远程主机。
**send()**:发送数据。
**recv()**:接收数据。
**sendto()**:发送数据到指定地址。
**recvfrom()**:从指定地址接收数据。
**shutdown()**:关闭套接字的某些功能。
**getsockname()**:获取套接字的本地地址。
**getpeername()**:获取套接字的对端地址。
一个简单的回显服务器和客户端程序
服务器程序
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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define PORT 8080 #define BUFFER_SIZE 1024 int main () { int server_fd, new_socket; struct sockaddr_in address ; int addrlen = sizeof (address); char buffer[BUFFER_SIZE] = {0 }; char *welcome_message = "Welcome to the echo server!\n" ; if ((server_fd = socket(AF_INET, SOCK_STREAM, 0 )) == 0 ) { perror("socket failed" ); exit (EXIT_FAILURE); } int opt = 1 ; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof (opt))) { perror("setsockopt" ); exit (EXIT_FAILURE); } 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)) < 0 ) { perror("bind failed" ); exit (EXIT_FAILURE); } if (listen(server_fd, 3 ) < 0 ) { perror("listen" ); exit (EXIT_FAILURE); } printf ("Server is listening on port %d...\n" , PORT); while (1 ) { if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0 ) { perror("accept" ); exit (EXIT_FAILURE); } send(new_socket, welcome_message, strlen (welcome_message), 0 ); while (1 ) { int bytes_read = read(new_socket, buffer, BUFFER_SIZE); if (bytes_read <= 0 ) { printf ("Client disconnected.\n" ); close(new_socket); break ; } send(new_socket, buffer, bytes_read, 0 ); memset (buffer, 0 , BUFFER_SIZE); } } return 0 ; }
客户端程序
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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define PORT 8080 #define BUFFER_SIZE 1024 int main () { int sock = 0 ; struct sockaddr_in serv_addr ; char buffer[BUFFER_SIZE] = {0 }; char *message = "Hello, Server!" ; if ((sock = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror("Socket creation error" ); exit (EXIT_FAILURE); } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); if (inet_pton(AF_INET, "127.0.0.1" , &serv_addr.sin_addr) <= 0 ) { perror("Invalid address/ Address not supported" ); exit (EXIT_FAILURE); } if (connect(sock, (struct sockaddr *)&serv_addr, sizeof (serv_addr)) < 0 ) { perror("Connection failed" ); exit (EXIT_FAILURE); } send(sock, message, strlen (message), 0 ); printf ("Message sent: %s\n" , message); int bytes_read = read(sock, buffer, BUFFER_SIZE); printf ("Message from server: %s\n" , buffer); close(sock); return 0 ; }
6. 进程间通信
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main () { int pipefd[2 ]; pid_t pid; char buf[100 ]; const char * message = "Hello from the parent process!" ; if (pipe(pipefd) == -1 ) { perror("pipe" ); exit (EXIT_FAILURE); } pid = fork(); if (pid == -1 ) { perror("fork" ); exit (EXIT_FAILURE); } if (pid == 0 ) { close(pipefd[1 ]); read(pipefd[0 ], buf, sizeof (buf)); printf ("Child process received: %s\n" , buf); close(pipefd[0 ]); exit (EXIT_SUCCESS); } else { close(pipefd[0 ]); write(pipefd[1 ], message, strlen (message) + 1 ); close(pipefd[1 ]); wait(NULL ); exit (EXIT_SUCCESS); } }
管道适用于父子进程间的通信
**mkfifo()**:创建FIFO(命名管道)。
写进程
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 #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main () { const char * fifo_path = "/tmp/myfifo" ; const char * message = "Hello from the FIFO writer!" ; int fd; if (mkfifo(fifo_path, 0666 ) == -1 ) { perror("mkfifo" ); exit (EXIT_FAILURE); } fd = open(fifo_path, O_WRONLY); if (fd == -1 ) { perror("open" ); exit (EXIT_FAILURE); } write(fd, message, strlen (message) + 1 ); close(fd); unlink(fifo_path); return 0 ; }
读进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> int main () { const char * fifo_path = "/tmp/myfifo" ; char buf[100 ]; int fd; fd = open(fifo_path, O_RDONLY); if (fd == -1 ) { perror("open" ); exit (EXIT_FAILURE); } read(fd, buf, sizeof (buf)); printf ("Received message: %s\n" , buf); close(fd); return 0 ; }
FIFO(命名管道)允许不相关的进程进行通信
7. 文件系统操作
**mount()**:挂载文件系统。
**umount()**:卸载文件系统。
8. 权限管理
**chmod()**:更改文件权限。
**chown()**:更改文件所有者。