当前位置: 首页 > news >正文

Linux 进程控制 基础IO

Linux 进程控制学习笔记

本节重点

  • 学习进程创建:fork() / vfork()
  • 学习进程等待
  • 学习进程程序替换:exec 函数族,微型 shell 实现原理
  • 学习进程终止:认识 $?

一、进程创建

1. fork() 函数初识

在 Linux 中,fork() 函数用于从一个已存在的进程中创建一个新进程。新进程称为子进程,原进程称为父进程。

函数原型:

C

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

返回值:

  • 在子进程中返回 0
  • 在父进程中返回子进程的 ID。
  • 如果出错,返回 -1

内核操作流程:

  1. 为子进程分配新的内存块和内核数据结构。
  2. 将父进程的部分数据结构内容拷贝至子进程。
  3. 将子进程添加到系统进程列表中。
  4. fork() 返回后,开始由调度器调度。

fork() 调用前后对比:

  • 调用前: 单个进程执行。
  • 调用后: 产生父子两个进程,它们拥有几乎相同的代码和数据空间(通过写时拷贝技术共享),但从 fork() 返回后,它们可以执行不同的代码路径。 调度器决定哪个进程先执行。

示例代码:

C

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> // For exit()int main(void) {pid_t pid;printf("Before: pid is %d\n", getpid()); [cite: 1]if ((pid = fork()) == -1) {perror("fork()");exit(1);}printf("After: pid is %d, fork return %d\n", getpid(), pid); [cite: 2]sleep(1);return 0;
}

运行结果分析:

输出会有三行:一行 “Before” 和两行 “After”。父进程打印 “Before” 和它自己的 “After”。子进程只打印它自己的 “After”,因为它从 fork() 之后开始执行,不会执行 fork() 之前的 printf 语句。

2. 写时拷贝 (Copy-on-Write)

fork() 之后,父子进程的代码段是共享的。数据段在不发生写入操作时也是共享的。当父进程或子进程尝试写入共享数据时,内核会为写入方创建一个该数据页的副本,从而实现父子进程数据段的独立性。这种机制称为写时拷贝。

3. fork() 常规用法
  • 一个父进程复制自己,使父子进程同时执行不同的代码段(例如,父进程等待客户端请求,子进程处理请求)。
  • 一个进程要执行一个不同的程序。子进程从 fork() 返回后,通常会调用 exec 系列函数来加载并执行新的程序。
4. fork() 调用失败的原因
  • 系统中的进程数量过多。
  • 单个用户的进程数量超过了系统限制。

二、进程终止

1. 进程退出场景
  • 代码运行完毕,结果正确。
  • 代码运行完毕,结果不正确。
  • 代码异常终止。
2. 进程常见退出方法

正常终止 (可通过 echo $? 查看进程退出码):

  1. main 函数返回。
  2. 调用 exit() 函数。
  3. 调用 _exit() 函数。

异常退出:

  • 通过信号终止,例如 Ctrl+C
3. _exit() 函数

函数原型:

C

#include <unistd.h>
void _exit(int status);

参数: status 定义了进程的终止状态,父进程可以通过 wait()waitpid() 来获取该值。虽然 statusint 类型,但只有低8位可以被父进程获取。例如,_exit(-1) 后,在终端执行 echo $? 会得到 255

_exit() 直接请求内核终止进程,不会执行用户定义的清理函数或刷新标准I/O缓冲区。

4. exit() 函数

函数原型:

C

#include <stdlib.h> // 注意头文件与 _exit 不同
void exit(int status);

exit() 函数在调用 _exit() 之前会执行以下操作:

  1. 执行用户通过 atexit()on_exit() 定义的清理函数。
  2. 关闭所有打开的流,并将所有缓存数据写入。
  3. 调用 _exit()

示例对比 exit()_exit()

  • 使用 exit(0)printf("hello"); 后会输出 “hello”。
  • 使用 _exit(0)printf("hello"); 后可能不会输出 “hello”,因为标准输出缓冲区可能未被刷新。
5. return 退出

main 函数中执行 return n; 等同于执行 exit(n);

三、进程等待

1. 进程等待的必要性
  • 防止僵尸进程: 子进程退出后,如果父进程没有对其进行处理(即调用 wait()waitpid() 回收子进程资源),子进程会变成僵尸进程,占用系统资源,可能导致内存泄漏。 僵尸进程无法被 kill -9杀死。
  • 获取子进程退出信息: 父进程需要知道子进程的任务完成情况,例如是否正常退出、退出码是多少。
2. 进程等待的方法
a. wait() 方法

函数原型:

C

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

返回值:

  • 成功:返回被等待的子进程 ID。
  • 失败:返回 -1参数:
  • status:输出型参数,用于获取子进程的退出状态。如果不关心,可以设置为 NULL
b. waitpid() 方法

函数原型:

C

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

返回值:

  • 正常返回:返回收集到的子进程的进程 ID。

  • 如果设置了 WNOHANG 选项,并且没有已退出的子进程可收集:返回 0

  • 调用出错:返回 -1参数:

  • pid
    

    • pid == -1:等待任一个子进程(与 wait() 等效)。
    • pid > 0:等待进程 ID 与 pid 相等的子进程。
  • status
    

    :与

    wait()
    

    中的

    status
    

    类似,用于获取子进程退出状态。

    • WIFEXITED(status):若为正常终止子进程返回的状态,则为真(用于判断进程是否正常退出)。
    • WEXITSTATUS(status):若 WIFEXITED(status) 非零,则提取子进程的退出码(低8位)。
  • options
    

    • WNOHANG:若 pid 指定的子进程没有结束,则 waitpid() 函数立即返回 0,不予以等待。若正常结束,则返回该子进程的 ID。

调用 wait() / waitpid() 的行为:

  • 如果子进程已经退出:wait()/waitpid() 会立即返回,释放子进程资源,并获取其退出信息。
  • 如果子进程存在且正常运行:进程可能会阻塞等待子进程退出(除非使用 WNOHANG)。
  • 如果不存在指定的子进程:立即出错返回。
3. 获取子进程 status

status 参数是一个输出型参数,由操作系统填充,不能简单地当作整型看待,应将其视为位图。只关注 status 的低16比特位:

  • 正常终止:
    • 位 0-7:为 0。
    • 位 8-15:为子进程的退出状态(即 exit()main return 的值)。
  • 被信号所杀:
    • 位 0-6:为导致进程终止的信号编号。
    • 位 7:如果为 1,表示产生了 core dump 文件。

宏进行判断和提取:

  • WIFEXITED(status):如果子进程正常终止,则为真。
  • WEXITSTATUS(status):如果 WIFEXITED(status) 为真,则此宏提取子进程的退出码。
  • WIFSIGNALED(status):如果子进程是因为一个未被捕获的信号终止的,则为真。
  • WTERMSIG(status):如果 WIFSIGNALED(status) 为真,则此宏提取导致子进程终止的信号编号。

测试代码示例(手动解析 status):

C

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // For sleep, fork, exitint main(void) {pid_t pid;if ((pid = fork()) == -1) {perror("fork");exit(1);}if (pid == 0) { // Child processsleep(2); // Simulate some work// exit(10); // Example of normal exitabort(); // Example of abnormal exit by signal (SIGABRT)} else { // Parent processint st;int ret = wait(&st); [cite: 10]if (ret > 0) {if ((st & 0x7F) == 0) { // Normal exit, bits 0-6 are 0 [cite: 11]printf("Child %d exited normally with code: %d\n", ret, (st >> 8) & 0xFF); [cite: 11]} else { // Abnormal exit by signalprintf("Child %d terminated by signal: %d\n", ret, st & 0x7F); [cite: 12]if (st & 0x80) { // Check core dump flag (bit 7)printf("Core dumped\n");}}} else {perror("wait");}}return 0;
}

使用宏的推荐代码:

C

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {pid_t pid;pid = fork(); [cite: 14]if (pid < 0) {printf("%s fork error\n", __FUNCTION__); [cite: 14]return 1;} else if (pid == 0) { // childprintf("child is run, pid is: %d\n", getpid()); [cite: 14]sleep(5);exit(257); // Exit code will be 1 (257 & 0xFF) [cite: 15]} else { // parentint status = 0;// 阻塞式等待pid_t ret = waitpid(-1, &status, 0); [cite: 15]printf("this is test for wait\n"); [cite: 15]if (ret > 0) {if (WIFEXITED(status)) { [cite: 16]printf("wait child %d success, child return code is:%d.\n", ret, WEXITSTATUS(status)); [cite: 16]} else if (WIFSIGNALED(status)) {printf("child %d terminated by signal %d\n", ret, WTERMSIG(status));} else {printf("wait child failed, return.\n"); [cite: 16]}} else {printf("waitpid error\n");return 1; [cite: 17]}}return 0; [cite: 18]
}

运行结果分析:

子进程 PID (例如 45110) 会被打印,然后父进程会等待5秒,打印 “this is test for wait”,最后打印子进程的退出码 (这里是 1,因为 257 & 0xFF = 1)。

4. 非阻塞等待

通过 waitpid()WNOHANG 选项实现非阻塞等待。父进程可以周期性地检查子进程是否退出,而不会一直阻塞。

示例代码 (非阻塞等待):

C

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>int main() {pid_t pid;pid = fork(); [cite: 19]if (pid < 0) {printf("%s fork error\n", __FUNCTION__); [cite: 19]return 1;} else if (pid == 0) { // childprintf("child is run, pid is: %d\n", getpid()); [cite: 20]sleep(5);exit(1); [cite: 20]} else { // parentint status = 0;pid_t ret = 0;do {ret = waitpid(pid, &status, WNOHANG); // 非阻塞式等待 [cite: 21]if (ret == 0) { [cite: 21]printf("child is running, parent is doing other things...\n");sleep(1);}} while (ret == 0); [cite: 21] // Loop until child exits or errorif (ret == pid) { // Child has exitedif (WIFEXITED(status)) { [cite: 22]printf("wait child %d success, child return code is:%d.\n", ret, WEXITSTATUS(status)); [cite: 22]} else if (WIFSIGNALED(status)) {printf("child %d terminated by signal %d\n", ret, WTERMSIG(status));}} else if (ret == -1) {perror("waitpid error");return 1;} else {printf("wait child failed, return.\n"); // Should not happen in this logic if ret > 0 [cite: 22]// return 1; [cite: 23] // Original code has return 1 here}}return 0; [cite: 23]
}

四、进程程序替换

1. 替换原理

当用 fork() 创建子进程后,子进程执行的是和父进程相同的程序(可能执行不同的代码分支)。如果子进程需要执行另一个程序,它会调用 exec 系列函数。当进程调用 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,并从新程序的启动例程开始执行。调用 exec 并不创建新进程,因此调用前后进程的 ID 不会改变。

替换过程示意图:

原进程的虚拟内存(代码段、数据段、堆栈)被新程序的相应部分替换。页表会更新以映射到新程序的物理内存页(通常从磁盘加载可执行文件ELF)。进程控制块(PCB)中的一些信息(如进程ID)保持不变。

2. exec 函数族

有六个以 exec 开头的函数,统称为 exec 函数:

C

#include <unistd.h>int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, ... /* (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

函数解释:

  • 如果调用成功,则加载新的程序从其启动代码开始执行,不再返回到原程序的调用点。
  • 如果调用出错,则返回 -1
  • 因此,exec 函数只有出错的返回值,没有成功的返回值。

命名规则理解:

  • l (list):表示参数采用参数列表的形式(可变参数,以 NULL 结尾)。
  • v (vector):表示参数采用字符串指针数组 (char *argv[]) 的形式(以 NULL 结尾)。
  • p (path):表示函数会自动在环境变量 PATH 指定的目录中搜索要执行的程序文件。
  • e (environment):表示允许调用者传递一个新的环境变量数组给被执行的程序。如果不带 e,则新程序继承当前进程的环境变量。

exec 函数特性总结:

函数名参数格式是否带路径 (自动搜索PATH)是否使用当前环境变量 (或自定义)
execl列表否 (需提供完整路径)是 (继承当前环境)
execlp列表是 (自动搜索PATH)是 (继承当前环境)
execle列表否 (需提供完整路径)否 (需自己组装环境变量)
execv数组否 (需提供完整路径)是 (继承当前环境)
execvp数组是 (自动搜索PATH)是 (继承当前环境)
execve数组否 (需提供完整路径)否 (需自己组装环境变量)

exec 调用示例:

C

#include <unistd.h>
#include <stdlib.h> // For exit()
#include <stdio.h>  // For perror()int main() {char *const argv_ps[] = {"ps", "-ef", NULL}; [cite: 26]char *const envp_custom[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL}; [cite: 26]// Example 1: execl - full path, list arguments// execl("/bin/ps", "ps", "-ef", NULL); [cite: 26]// perror("execl failed"); // This line is reached only if execl fails// Example 2: execlp - command name (searches PATH), list arguments// execlp("ps", "ps", "-ef", NULL); [cite: 26]// perror("execlp failed");// Example 3: execle - full path, list arguments, custom environment// execle("/bin/ps", "ps", "-ef", NULL, envp_custom); // Note: "ps" may not be /bin/ps [cite: 27]// To be precise, use the path found by 'which ps' or a known absolute path.// For demonstration, if "ps" is in /usr/bin:// execle("/usr/bin/ps", "ps", "-ef", NULL, envp_custom);// perror("execle failed");// Example 4: execv - full path, array arguments// execv("/bin/ps", argv_ps); [cite: 27]// perror("execv failed");// Example 5: execvp - command name (searches PATH), array arguments// execvp("ps", argv_ps); [cite: 27]// perror("execvp failed");// Example 6: execve - full path, array arguments, custom environment (SYSTEM CALL)execve("/bin/ps", argv_ps, envp_custom); [cite: 27]perror("execve failed"); // This line is reached only if execve failsexit(0); // Should not be reached if exec succeeds
}

注意: execve 是真正的系统调用(在 man 手册第2节),其他五个函数都是库函数,最终都会调用 execve(在 man 手册第3节)。 它们之间的关系可以简化为:其他 exec 函数通过不同的方式准备好参数和环境,最终调用 execve

3. 实现简易 Shell

一个典型的 Shell 交互过程(如 bash)如下:

  1. Shell 读取用户输入的命令(例如 "ls")。
  2. Shell 创建一个新的子进程 (fork())。
  3. 子进程使用 exec 函数族中的一个(通常是 execvp,因为它会自动搜索 PATH 并使用当前环境)来执行用户指定的命令。
  4. 父进程(Shell)等待子进程结束 (wait()waitpid())。
  5. 子进程结束后,Shell 再次提示用户输入,循环此过程。

简易 Shell 实现步骤:

  1. 获取命令行: 显示提示符,读取用户输入。
  2. 解析命令行: 将输入的字符串分割成命令名和参数列表。
  3. 建立子进程: 调用 fork()
  4. 替换子进程: 在子进程中,调用 execvp() 执行解析后的命令。
  5. 父进程等待: 父进程调用 waitpid() 等待子进程结束。

示例代码 (MiniShell):

C

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h> // For waitpid and W... macros
#include <ctype.h>    // For isspace#define MAX_CMD 1024
char command[MAX_CMD]; [cite: 29]// Function to get command from user
int do_face() { [cite: 29]memset(command, 0x00, MAX_CMD); [cite: 29]printf("minishell$ ");fflush(stdout); [cite: 29]if (fgets(command, MAX_CMD, stdin) == NULL) { // Use fgets for safety// Handle EOF (Ctrl+D)if (feof(stdin)) {printf("\nExiting minishell.\n");exit(0);}perror("fgets failed");return -1;}// Remove trailing newline if presentcommand[strcspn(command, "\n")] = 0;if (strlen(command) == 0) { // Handle empty inputreturn -1;}return 0;
}// Function to parse the command string into arguments
char **do_parse(char *buff) { [cite: 30]int argc = 0;static char *argv[32]; // Max 31 arguments + NULL terminator [cite: 30]char *ptr = buff; [cite: 31]while (*ptr != '\0') {// Skip leading whitespacewhile (isspace((unsigned char)*ptr)) {ptr++;}if (*ptr == '\0') break; // End of string after whitespaceargv[argc++] = ptr; // Start of an argument [cite: 31]// Find end of the argumentwhile (*ptr != '\0' && !isspace((unsigned char)*ptr)) {ptr++; [cite: 31]}if (*ptr != '\0') { // If not end of string, it's whitespace*ptr = '\0'; // Terminate the argument string [cite: 32]ptr++;       // Move to the next character}}argv[argc] = NULL; // Null-terminate the argument list [cite: 32]return argv;
}// Function to execute the command
int do_exec(char *buff) { [cite: 32]char **argv = {NULL};pid_t pid;// Basic "exit" command handlingif (strcmp(buff, "exit") == 0) {printf("Exiting minishell via 'exit' command.\n");exit(0);}pid = fork(); [cite: 32]if (pid < 0) {perror("fork failed");return -1;}if (pid == 0) { // Child process [cite: 33]argv = do_parse(buff); [cite: 33]if (argv[0] == NULL) { // No command after parsing (e.g., only whitespace)exit(1); // Or handle more gracefully [cite: 33]}execvp(argv[0], argv); [cite: 33]// If execvp returns, an error occurredperror("execvp failed");exit(127); // Standard exit code for command not found or exec error} else { // Parent processint status;waitpid(pid, &status, 0); [cite: 33]// Optionally, check child's exit status here// if (WIFEXITED(status)) {//     printf("Child exited with status %d\n", WEXITSTATUS(status));// }}return 0;
}int main(int argc, char *argv_main[]) { [cite: 34]while (1) {if (do_face() < 0) {// If input is empty or error, just prompt againcontinue; [cite: 34]}do_exec(command); [cite: 34]}return 0; // Should not be reached [cite: 34]
}

五、函数与进程的相似性

Linux 将结构化程序设计中函数间通过调用、参数传递、返回值的通信模式,扩展到了程序(进程)之间:

  • 函数调用 (call/return) vs. 进程创建与执行 (fork/execwait/exit)
    • 一个C程序由多个函数组成。函数可以调用其他函数,传递参数,被调用函数执行操作后返回值。每个函数有其局部变量。
    • 类似地,一个进程可以通过 fork() 创建子进程,然后子进程通过 exec() 执行新的程序(相当于调用另一个“程序函数”),父进程可以向子进程传递参数(通过 execargvenvp)。被调用的程序执行操作后通过 exit(n) 返回一个值,调用它的父进程可以通过 wait(&status) 来获取这个返回值。

Linux 基础 I/O

一、C语言文件I/O回顾 (库函数)

  • 核心接口: fopen(), fclose(), fread(), fwrite().

  • 文件指针: FILE* (如 stdin, stdout, stderr).

  • 打开模式

    :

    • "r": 读 (文件需存在)
    • "w": 写 (清空或创建)
    • "a": 追加 (文件末尾写或创建)
    • "r+": 读写 (文件需存在)
    • "w+": 读写 (清空或创建)
    • "a+": 读追加 (读从头,写从尾)
  • 示例 (写文件)

    :

    C

    #include <stdio.h>
    #include <string.h>
    int main() {FILE *fp = fopen("myfile", "w");if (!fp) { /* 错误处理 */ }const char *msg = "hello bit!\n";fwrite(msg, strlen(msg), 1, fp);fclose(fp);return 0;
    }
    
  • 示例 (读文件)

    :

    C

    #include <stdio.h>
    #include <string.h>
    int main() {FILE *fp = fopen("myfile", "r");if (!fp) { /* 错误处理 */ }char buf[1024];ssize_t s = fread(buf, 1, sizeof(buf)-1, fp); // 读到bufif (s > 0) {buf[s] = 0;printf("%s", buf);}fclose(fp);return 0;
    }
    

二、系统调用文件I/O

  • 核心接口: open(), close(), read(), write(), lseek().

    • 这些是操作系统提供的直接接口。C库函数 (fopen 等) 封装了这些系统调用。
  • open():

    C

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    // int open(const char *pathname, int flags);
    // int open(const char *pathname, int flags, mode_t mode);
    
    • pathname: 文件路径。

    • flags
      

      : 打开选项 (按位或

      |
      

      组合):

      • 必选其一: O_RDONLY (只读), O_WRONLY (只写), O_RDWR (读写).
      • 可选: O_CREAT (不存在则创建, 需 mode), O_APPEND (追加写), O_TRUNC (清空文件内容, 常与 O_WRONLY 结合).
    • mode: 文件权限 (如 0644), 仅当 O_CREAT 时有效。受 umask 影响。

    • 返回值: 文件描述符 (fd, 非负整数);失败返回 -1.

  • write():

    C

    #include <unistd.h>
    // ssize_t write(int fd, const void *buf, size_t count);
    
    • fd: 文件描述符。
    • buf: 数据缓冲区。
    • count: 期望写入的字节数。
    • 返回值: 实际写入的字节数;失败返回 -1.
  • read():

    C

    #include <unistd.h>
    // ssize_t read(int fd, void *buf, size_t count);
    
    • fd: 文件描述符。
    • buf: 存储读取数据的缓冲区。
    • count: 期望读取的字节数。
    • 返回值: 实际读取的字节数 (0 表示文件末尾);失败返回 -1.
  • close():

    C

    #include <unistd.h>
    // int close(int fd);
    
    • 关闭文件描述符。
  • 示例 (系统调用写文件):

    C

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdio.h> // For perrorint main() {umask(0); // 确保权限设置不受掩码影响int fd = open("myfile_sys", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (fd < 0) { perror("open"); return 1; }const char *msg = "hello syscall!\n";ssize_t s = write(fd, msg, strlen(msg));if (s < 0) { perror("write"); /* 处理写入错误 */ }close(fd);return 0;
    }
    

三、文件描述符 (fd)

  • 概念: 内核中用于标识已打开文件的小整数。每个进程维护一个文件描述符表。

  • 默认fd

    :

    • 0: 标准输入 (stdin) - 通常是键盘。
    • 1: 标准输出 (stdout) - 通常是显示器。
    • 2: 标准错误 (stderr) - 通常是显示器。
  • 分配规则: 分配当前未使用的最小非负整数作为新的 fd。

  • task_struct -> files_struct -> fd_array[] (指向 file 结构体): 进程通过 fd 在其 fd_array 中找到对应的 file 结构体,该结构体包含文件信息。

四、重定向

  • 本质
    修改文件描述符表中 fd 指向的
    file
    

    结构体。

    • 例如,关闭 fd 1 (标准输出),然后打开一个文件,新文件会获得 fd 1。此时,所有写入 fd 1 的数据将进入该文件而非显示器。
  • 示例 (关闭1实现输出重定向)

    :

    C

    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h> // For printf, fflush, perror
    #include <stdlib.h> // For exitint main() {close(1); // 关闭标准输出int fd = open("myfile_redir", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (fd < 0) { perror("open"); return 1; }//此时 fd 通常会是 1printf("fd: %d\n", fd); // 这行会写入 myfile_redirprintf("This goes to the file!\n");fflush(stdout); // 确保 printf 的内容被刷新close(fd);return 0;
    }
    
  • dup2(int oldfd, int newfd)
    核心重定向函数。
    • 使 newfd 指向 oldfd 所指向的 file 结构体。
    • 如果 newfd 已打开,会先关闭它。
    • 如果 oldfd == newfd,则不执行任何操作。
    • 示例: dup2(log_fd, 1); // 将标准输出重定向到 log_fd 指向的文件。

五、FILE 结构体与 fd 的关系

  • C标准库的 FILE 结构体内部封装了文件描述符 fd (通常名为 _filenofileno)。

  • 用户级缓冲区

    :

    • printf
      

      ,

      fwrite
      

      (库函数) 自带用户级缓冲区。

      • 写入文件时:全缓冲。
      • 写入显示器时:行缓冲。
    • write (系统调用) 不带用户级缓冲区 (数据直接或通过内核缓冲区写出)。

    • 重定向影响: printf 到普通文件时,缓冲方式可能从行缓冲变为全缓冲。

    • fork() 与缓冲区: fork() 后,父子进程会各自拥有缓冲区的副本 (写时拷贝)。若缓冲区未刷新,可能导致数据重复输出。

六、文件系统与 Inode

  • 文件元数据 (Inode): 存储文件的属性信息 (大小、权限、所有者、时间戳、数据块位置等),文件名不存储在 inode 中。每个文件唯一对应一个 inode。

  • 目录: 本质是文件,存储文件名和对应 inode 号的列表。

  • ls -i: 查看文件的 inode 号。

  • stat 命令: 查看文件的详细元数据,包括 inode 号。

  • 文件系统结构 (简要)

    :

    • Block Group: 将磁盘分区划分为多个块组。
    • Super Block: 存放文件系统全局信息。
    • Inode Table: 存放所有 inode。
    • Data Blocks: 存放文件实际内容。
  • 创建文件流程

    :

    1. 分配空闲 inode,记录文件属性。
    2. 分配空闲数据块,存储文件内容。
    3. 在 inode 中记录数据块的分配情况。
    4. 在目录文件中添加 “文件名 -> inode号” 的条目。

七、硬链接与软链接

  • 硬链接 (Hard Link)

    :

    • 多个文件名指向同一个 inode。
    • ln source_file hard_link_name
    • inode 中有硬链接计数。删除文件时,计数减1,当计数为0且无进程打开该文件时,才真正删除文件数据。
    • 不能对目录创建硬链接,不能跨文件系统。
  • 软链接 (Symbolic Link / Soft Link)

    :

    • 一个独立的文件,其内容是另一个文件的路径名。
    • ln -s target_file_or_dir soft_link_name
    • 类似于快捷方式。删除软链接不影响目标文件。若目标文件被删除,软链接失效 (悬空链接)。
    • 可以对目录创建,可以跨文件系统。
  • 文件时间戳

    :

    • Access: 最后访问时间。
    • Modify: 文件内容最后修改时间。
    • Change: 文件属性或 inode 最后修改时间。

八、静态库与动态库

  • 静态库 (.a)

    :

    • 编译链接时,库代码被完整复制到可执行文件中。

    • 优点: 运行不依赖外部库。

    • 缺点: 可执行文件大,库更新需重新编译。

    • 制作

      :

      1. gcc -c myfunc1.c -o myfunc1.o (编译源文件到目标文件)
      2. ar rc libmylib.a myfunc1.o myfunc2.o (打包目标文件成静态库)
    • 使用: gcc main.c -L. -lmylib (-L. 指定库路径, -lmylib 指定库名 mylib)

  • 动态库 (.so, Shared Object)

    :

    • 编译链接时,只记录对库函数的引用。程序运行时,由操作系统加载库代码到内存并链接。

    • 优点: 可执行文件小,多程序共享库代码 (节省内存、磁盘),库更新方便。

    • 缺点: 运行依赖外部库。

    • 制作

      :

      1. gcc -fPIC -c myfunc1.c -o myfunc1.o (-fPIC: 生成位置无关代码)
      2. gcc -shared -o libmylib.so myfunc1.o myfunc2.o (创建动态库)
    • 使用 (编译): gcc main.c -L. -lmylib

    • 使用 (运行)
      需让系统能找到
      .so
      

      文件:

      1. 拷贝到系统共享库路径 (如 /usr/lib, /usr/local/lib)。
      2. 设置环境变量 LD_LIBRARY_PATH (如 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/your/lib)。
      3. 配置 /etc/ld.so.conf.d/ 下的配置文件,然后运行 ldconfig 更新缓存。
  • 库命名约定: lib<库名>.alib<库名>.so。链接时使用 -l<库名>

这基本涵盖了文档中的核心内容和操作,希望能满足您的复习需求。

相关文章:

Linux 进程控制 基础IO

Linux 进程控制学习笔记 本节重点 学习进程创建&#xff1a;fork() / vfork()学习进程等待学习进程程序替换&#xff1a;exec 函数族&#xff0c;微型 shell 实现原理学习进程终止&#xff1a;认识 $? 一、进程创建 1. fork() 函数初识 在 Linux 中&#xff0c;fork() 函…...

三、Hive DDL数据库操作

在 Apache Hive 中&#xff0c;数据库 (Database)&#xff0c;有时也被称为模式 (Schema)&#xff0c;是组织和管理 表及其他对象的基本命名空间单元。熟练掌握数据库层面的数据定义语言 (DDL) 操作&#xff0c;是构建清晰、有序的 Hive 数据仓库的第一步。本篇笔记将详细梳理 …...

C++ string初始化、string赋值操作、string拼接操作

以下介绍了string的六种定义方式&#xff0c;还有很多&#xff0c;这个只是简单举例 #include<iostream>using namespace std;int main() {//1 无参构造string s1;cout << s1 << endl;//2 初始化构造string s2 ({h, h, l, l, o});cout << s2 <<…...

java.util.Timer

知识点详细说明 java.util.Timer 是Java早期提供的定时任务调度工具,用于在指定延迟后或按固定间隔执行任务。以下是其核心知识点: 1. 核心组成 Timer类:负责调度任务,内部维护一个任务队列和后台线程。TimerTask类:抽象类,需继承并实现run()方法定义任务逻辑。2. 核心方…...

SQlite数据库

介绍 基本信息&#xff1a;是一款轻量级的嵌入式关系型数据库管理系统。 主要特点&#xff1a;SQLite的源码是C语言&#xff0c;其源代码完全开发&#xff0c;SQLite第一个Alpha版本诞生于2000年5月&#xff0c;他是一个轻量级的嵌入式数据库。零配置&#xff0c;无需安装和配…...

什么是 ANR 如何避免它

一、什么是 ANR&#xff1f; ANR&#xff08;Application Not Responding&#xff09; 是 Android 系统在应用程序主线程&#xff08;UI 线程&#xff09;被阻塞超过一定时间后触发的错误机制。此时系统会弹出一个对话框提示用户“应用无响应”&#xff0c;用户可以选择等待或强…...

当虚拟吞噬现实——《GTA6》结合技术

‌ ‌标题&#xff1a;当虚拟吞噬现实——《GTA6》的万言书&#xff1a;一部数字文明的启示录‌ ‌&#xff08;万字深度解析&#xff0c;拆解技术、叙事与社会学的三重革命&#xff09;‌ ‌一、序章&#xff1a;游戏史的奇点时刻‌ 1. ‌从像素暴动到文明模拟&#xff1a;G…...

pandas读取pymysql和解析excel的一系列问题(版本不匹配)

pandas读取pymysql和解析excel的一系列问题&#xff0c;大部分都是版本不匹配导致的 尤其是pandas,numpy,pymysql,openpyxl不匹配导致 from sqlalchemy import create_engine import numpy as np import pandas as pd conncreate_engine("mysqlpymysql://user:passhost:3…...

【安装配置教程】ubuntu安装配置Kodbox

目录 一、引言 二、环境配置 1. 服务器配置​ 2. 必备组件​ 三、安装基础环境​ 1. 安装 PHP 8.1 及扩展​ 2. 安装 MySQL 数据库 3.安装 Redis&#xff08;可选&#xff0c;提升缓存性能&#xff09; 4. 配置nginx文件 4.1. 创建 Kodbox 站点目录​ 4.2. 编写 Ng…...

模型过拟合是什么?

模型过拟合的详细解析 一、定义与本质 过拟合(Overfitting)是机器学习与统计学中的核心问题,指模型在训练数据上表现优异,但在未见过的新数据(如测试集或实际应用数据)上泛化能力显著下降的现象。其本质在于模型过度捕捉了训练数据中的噪声、随机波动或非典型细节,而非…...

服务器mysql连接我碰到的错误

搞了2个下午&#xff0c;总算成功了 我在服务器上使用docker部署了java项目与mysql&#xff0c;但mysql连接一直出现问题 1.首先&#xff0c;我使用的是localhost连接&#xff0c;心想反正都在服务器上吧。 jdbc:mysql://localhost:3306/fly-bird?useSSLfalse&serverTime…...

数电课设·交通信号灯(Quartus Ⅱ)

远书归梦两悠悠&#xff0c;只有空床敌素秋。 阶下青苔与红树&#xff0c;雨中寥落月中愁。 ————《端居》 【唐】 李商隐 目录 交通信号灯 要点剖析&#xff1a; 端口说明&#xff1a; 代码展示&#xff1a;&…...

单片机-STM32部分:13、PWM

飞书文档https://x509p6c8to.feishu.cn/wiki/NjhuwbVP7iaEOikVK95cmJNLnWf PWM&#xff08;Pulse Width Modulation&#xff09;脉冲宽度调制&#xff0c;是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。它是把每一脉冲宽度均相等的脉冲列作为PWM波形&am…...

HTTP 错误状态码以及常用解决方案

以下是常见 HTTP 错误状态码及其解决方案的对比表格&#xff0c;按客户端&#xff08;4xx&#xff09;和服务端&#xff08;5xx&#xff09;分类&#xff1a; HTTP 错误码对比表 一、客户端错误&#xff08;4xx&#xff09; 状态码含义常见原因解决方案400Bad Request请求参…...

巧用promise.race实现nrm镜像源切换----nbsl

今天是母亲节祝全天的母亲节日快乐奥 引言 在复习Promise知识点时&#xff0c;发现Promise.race在实际开发中应用较少&#xff0c;于是深入思考了它的应用场景。最近使用nrm&#xff08;npm镜像源切换工具&#xff09;时&#xff0c;想到每次都需要手动切换镜像源来测试哪个更…...

Python基础语法(中)

顺序语句 默认情况下&#xff0c;Python的代码执行顺序是从上往下执行的。 形如下面这样的代码&#xff0c;执行的结果只能是123&#xff0c;而不是321 print(1) print(2) print(3) 条件语句 Python 中使用 if else 关键字表示条件语句 &#xff08;1&#xff09;if if e…...

【Part 2安卓原生360°VR播放器开发实战】第四节|安卓VR播放器性能优化与设备适配

《VR 360全景视频开发》专栏 将带你深入探索从全景视频制作到Unity眼镜端应用开发的全流程技术。专栏内容涵盖安卓原生VR播放器开发、Unity VR视频渲染与手势交互、360全景视频制作与优化&#xff0c;以及高分辨率视频性能优化等实战技巧。 &#x1f4dd; 希望通过这个专栏&am…...

java笔记06

TreeMap的基本使用 TreeMapTreeMap 跟 TreeSet 底层原理一样&#xff0c;都是红黑树结构的。由键决定特性&#xff1a;不重复、无索引、可排序可排序&#xff1a;对键进行排序。注意&#xff1a;默认按照键的从小到大进行排序&#xff0c;也可以自己规定键的排序规则 代码书写…...

Problem E: 实现冒泡排序(内存优化)

1.题目描述 输入任意顺序的整数序列&#xff0c;输出结果为从小到大的排序结果 2.输入描述 输入一个整数序列&#xff0c;整数之间用空格隔开&#xff0c;输入完最后一个整数&#xff0c;回车 3.输出描述 从小到大的排序结果 4.样例 提示&#xff1a;注意&#xff0c;主…...

大模型对时尚穿搭体验的革新与重塑

在大模型深度介入时尚穿搭领域后&#xff0c;人们的穿搭体验迎来了前所未有的变革。它不再局限于单纯提供搭配方案&#xff0c;而是全方位渗透进时尚穿搭的各个环节&#xff0c;从决策过程到实际穿着感受&#xff0c;都在悄然改变着人们与时尚互动的方式。 打破信息壁垒&#…...

第6讲、全面拆解Encoder、Decoder内部模块

全面拆解 Transformer 架构&#xff1a;Encoder、Decoder 内部模块解析&#xff08;附流程图小测验&#xff09; 关键词&#xff1a;Transformer、Encoder、Decoder、Self-Attention、Masked Attention、位置编码、残差连接、多头注意力机制 Transformer 自 2017 年诞生以来&am…...

Linux电脑本机使用小皮面板集成环境开发调试WEB项目

开发调试WEB项目&#xff0c;有时开发环境配置繁琐&#xff0c;可以使用小皮面板集成环境。 小皮面板官网&#xff1a; https://www.xp.cn/1.可以使用小皮面板安装脚本一键安装。登陆小皮面板管理后台 2.在“软件商店”使用LNMP一键部署集成环境。 3.添加网站&#xff0c;本…...

数据仓库Hive

1.数据仓库 1.1数据仓库的概念 数据仓库&#xff08;Data Warehouse&#xff09;是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合&#xff0c;用于支持管理决策。 面向主题。操作型数据库的数据组织面向事务处理任务&#xff0c;而数据仓库中的数据按照一定的…...

嵌入式学习笔记 - STM32 ADC,多重转换,内部参考电压,

一 多个ADC器件&#xff0c;多重转换速率 每个型号MCU通常由多个ADC器件&#xff0c;比如STM32F4有三个ADC器件&#xff0c;每个ADC器件有一个最大转换速率&#xff0c;一般为25Mhz&#xff0c;即一个ADC器件每秒最多转换25M次&#xff0c;两次转换之间需要有时间间隔&#xf…...

精读计算机体系结构基础 第三章 特权指令系统

3. 1 特权指令系统简介 想象一下&#xff0c;计算机就像一个大楼&#xff0c;而这个大楼有很多不同的楼层。每个楼层都有不同的功能和使用者。最上面的楼层是应用层&#xff0c;那里住着各种各样的应用程序&#xff0c;比如你用来写作的文字处理软件、用来浏览网页的浏览器等等…...

库室多功能控制器

应急供电保障​ 内置智能备电系统&#xff0c;市电中断时即刻无缝切换&#xff0c;为设备持续稳定供电&#xff0c;确保关键场景用电无忧。​ 超高性价比​ 集门禁、报警、环控等专业功能于一体&#xff0c;相比同类产品&#xff0c;以更优的价格提供更全面、更强大的解决方…...

使用FastAPI和React以及MongoDB构建全栈Web应用07 FastAPI实现经典三层架构

一、单文件简单实现 1.1 开发用户增删改查接口 main.py from fastapi import FastAPI, Request, Query, HTTPException from fastapi.responses import JSONResponse from motor.motor_asyncio import AsyncIOMotorClient from pydantic import BaseModel from bson import …...

《设计模式之禅》笔记

:::info &#x1f4a1; 根据 遗忘曲线&#xff1a;如果没有记录和回顾&#xff0c;6天后便会忘记75%的内容 读书笔记正是帮助你记录和回顾的工具&#xff0c;不必拘泥于形式&#xff0c;其核心是&#xff1a;记录、翻看、思考::: 书名设计模式之禅作者秦小波状态已读完简介深刻…...

JavaScript 循环语句全解析:选择最适合的遍历方式

循环是编程中处理重复任务的核心工具。JavaScript 提供了多种循环语句&#xff0c;每种都有其适用场景和独特优势。本文将深入解析 JavaScript 的 6 种核心循环语句&#xff0c;通过实际示例帮助你精准选择合适的循环方案。 一、基础循环三剑客 1. for 循环 经典索引控制 ja…...

远程服务器pycharm运行tensorboard显示训练轮次图

本文仅针对远程服务器的情况 首先在远程服务器端 首先打开xshell&#xff0c;然后激活自己的虚拟环境 baekee这是我的&#xff01; conda activate baekee然后cd进去你运行的文件所在的目录 cd /tmp/pycharm_project_732这个项目也是我的&#xff01; 然后一个很重要的事情…...

Nginx 使用 Keepalived 搭建 nginx 高可用

一、环境准备 两台装有 nginx 的 CentOS 虚拟机。 [rootnginx1 ~]# echo "192.168.40.81 say Hello" > /usr/local/nginx/html/index.html [rootnginx2 ~]# echo "192.168.40.82 say Hello" > /usr/local/nginx/html/index.html 二、原理 Keepaliv…...

A1062 PAT甲级JAVA题解 Talent and Virtue

About 900 years ago, a Chinese philosopher Sima Guang wrote a history book in which he talked about peoples talent and virtue. According to his theory, a man being outstanding in both talent and virtue must be a "sage&#xff08;圣人&#xff09;"…...

数据指标和数据标签

数据指标和数据标签是数据管理与分析中的两个重要概念&#xff0c;它们在用途、形式和应用场景上有显著区别。以下是两者的详细对比&#xff1a; 1. 核心定义 维度数据指标&#xff08;Data Metrics&#xff09;数据标签&#xff08;Data Tags/Labels&#xff09;定义量化衡量…...

常见的排序算法(Java版)简单易懂好上手!!

排序 “排序”顾名思义就是把乱序的变成有序的&#xff0c;就像我们玩斗地主这种牌类游戏时&#xff0c;我们拿到牌都会将牌给排序一下&#xff0c;更好的在对局中方便思考和观察&#xff0c;我们排序算法也亦是如此。 文章目录 排序一、冒泡排序二、选择排序三、插入排序四、…...

用统计零花钱的例子解释:Shuffle 是啥?

举个栗子 &#x1f330;&#xff1a;统计全班同学的零花钱总和 假设你是班长&#xff0c;全班有 4个小组&#xff0c;每个小组记录了自己的零花钱情况&#xff1a; 第1组&#xff1a;张三(5元)、李四(3元)、张三(2元) 第2组&#xff1a;王五(4元)、张三(1元)、李四(2元) …...

Kafka topic 中的 partition 数据倾斜问题

在 Kafka 中&#xff0c;如果一个 Topic 有多个 Partition&#xff0c;但这些 Partition 中的消息数量或流量分布不均衡&#xff0c;就会出现 数据倾斜&#xff08;Data Skew&#xff09; 的问题。 ✅ 什么是数据倾斜&#xff1f; 数据倾斜指的是&#xff1a; 某些 Partitio…...

Python基础总结(十)之函数

Python函数 函数是Python中也是非常重要的&#xff0c;函数是带名字的代码块&#xff0c;用于完成具体的工作。要执行函数定义的特定任务&#xff0c;可调用该函数。 一、函数的定义 函数的定义要使用def关键字&#xff0c;def后面紧跟函数名&#xff0c;缩进的为函数的代码…...

macOS 15 (Sequoia) 解除Gatekeeper限制

macOS 15 (Sequoia) 解除Gatekeeper限制指南 问题描述 在macOS 15中执行sudo spctl --global-disable命令后&#xff0c;系统提示&#xff1a; Globally disabling the assessment system needs to be confirmed in System Settings 但隐私与安全性界面未显示"任何来源&…...

【Flask开发踩坑实录】pip 安装报错:“No matching distribution found” 的根本原因及解决方案!

关键词&#xff1a;pip 报错、镜像源问题、flask-socketio、Python开发环境、安装失败 作者&#xff1a;未名编程 &#xff5c; 更新时间&#xff1a;2025.05.11 &#x1f4cc; 引言&#xff1a;一场莫名其妙的 pip 安装失败 最近在开发一个基于 Flask 的图像检索网站时&#…...

50.辐射抗扰RS和传导抗扰CS测试环境和干扰特征分析

辐射抗扰RS和传到抗扰CS测试环境和干扰特征分析 1. 辐射抗扰RS2. 传导抗扰CS 1. 辐射抗扰RS 辐射抗扰RS考察对外界电磁场干扰得抗扰能力&#xff0c;测试频段为80MHz~2000MHz&#xff0c;用1KHz得正弦波进行调幅&#xff0c;在电波暗室内进行。测试标准&#xff1a;IEC 61000-…...

零基础玩转sqlmap - 从入门到摸清数据库

sqlmap 包下载链接&#xff1a;https://pan.quark.cn/s/a6ab2586f77e 基本操作 最简单的用法&#xff1a;sqlmap -u "网址" - 直接测试这个网址有没有SQL注入漏洞 带参数的测试&#xff1a;如果网址后面有参数&#xff0c;比如 id1&#xff0c;sqlmap会自动检测 指…...

AI面经总结-试读

写在前面 该面经于2022年秋招上岸后耗时一个半月整理完毕&#xff0c;目前涵盖Python、基础理论、分类与聚类、降维、支持向量机SVM、贝叶斯|决策树、KNN、Boosting&Bagging、回归、代价函数与损失函数、激活函数、优化函数、正则优化、初始化与归一化、卷积、池化、传统图…...

python打卡day22@浙大疏锦行

复习日 仔细回顾一下之前21天的内容&#xff0c;没跟上进度的同学补一下进度。 作业&#xff1a; 自行学习参考如何使用kaggle平台&#xff0c;写下使用注意点&#xff0c;并对下述比赛提交代码 一、数据预处理 import pandas as pd import numpy as np import matplo…...

网络安全设备配置与管理-实验5-p150虚拟防火墙配置

实验5-p150虚拟防火墙配置 做不出来可以把项目删掉再新建。 实验六多加几步配置静态路由表就行。 文章目录 实验5-p150虚拟防火墙配置1. 实验目的2. 实验任务3. 实验设备4. 实验拓扑图和设备接口5. 实验命令与步骤1. 连线与配置2. 实验验证 思考题 1. 实验目的 通过该实验掌握…...

数值运算的误差估计

数值运算的误差估计 设两个近似数 x 1 ∗ x_1^* x1∗​与 x 2 ∗ x_2^* x2∗​的误差限分别为 ε ( x 1 ∗ ) \varepsilon(x_{1}^{*}) ε(x1∗​)和 ε ( x 2 ∗ ) \varepsilon(x_{2}^{*}) ε(x2∗​) 误差限满足一下运算法则&#xff1a; 和差运算的误差限&#xff1a; 设 y …...

HCIP-BGP实验一

一&#xff1a;拓扑图 二&#xff1a;需求分析&#xff1a; 保证R1-R5的环回地址相互能够通讯。 分析&#xff1b; 1.IP的配置 2.R2-R4完成IGP配置&#xff0c;配置OSPF 3.完成BGP配置。 4.优化配置&#xff0c;包括下一跳的选择&#xff0c;IBGP对等体建邻的IP地址。 三…...

linux内核pinctrl/gpio子系统驱动笔记

目录 一、简单介绍二、主要源码文件和目录gpio子系统pinctrl子系统两个子系统之间的关系设备树例子 三、主要的数据结构gpio子系统pinctrl子系统 四、驱动初始化流程五、难点说明 一、简单介绍 GPIO子系统: Linux GPIO子系统是Linux内核中负责处理GPIO&#xff08;通用输入输出…...

Qt—多线程基础

一、QThread 1.为什么使用多线程 在默认情况下&#xff0c;Qt使用的是单线程&#xff0c;当你启动一个 Qt 应用程序时&#xff0c;它会运行在一个单一的主线程&#xff08;也被称为 GUI 线程&#xff09;中。这个主线程负责处理所有的 GUI 事件和界面渲染。 但在一些其他情况下…...

HTML5表格语法格式详解

HTML5 表格的基本结构 HTML5 表格由 <table> 标签定义&#xff0c;表格中的每一行由 <tr> 标签定义&#xff0c;表头单元格由 <th> 标签定义&#xff0c;数据单元格由 <td> 标签定义。表格的基本结构如下&#xff1a; <table><tr><th…...

《Go小技巧易错点100例》第三十三篇

​ Validator自定义校验规则 Go语言中广泛使用的validator库支持通过结构体标签定义校验规则。当内置规则无法满足需求时&#xff0c;我们可以轻松扩展自定义校验逻辑。 示例场景&#xff1a;验证用户年龄是否成年&#xff08;≥18岁&#xff09; type User struct {Age in…...