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

【C语言系统编程】【第一部分:操作系统知识】1.1.操作系统原理

第一部分:操作系统知识

1.1 操作系统原理
1.1.1 进程管理
1.1.1.1 进程的概念与生命周期

进程是程序在计算机中的一次执行实例,包括了程序的代码、数据、以及运行的上下文环境。进程管理是操作系统的核心任务之一。

  • 作用:管理所有执行中的进程,确保它们高效地使用系统资源。
  • 生命周期
    • 创建:通过系统调用(如 fork)新建进程。
    • 就绪:进程已经准备好运行,等待 CPU 资源。
    • 运行:进程正在 CPU 上执行。
    • 阻塞:进程等待某一条件(如 I/O 操作)完成。
    • 终止:进程结束,释放占用的资源。
1.1.1.2 创建与终止进程(fork, exec, exit
  • fork:创建一个新进程,新进程是父进程的副本。新的子进程将复制父进程的地址空间,但拥有不同的 PID。子进程和父进程的执行顺序由调度器决定。

    #include <stdio.h>
    #include <unistd.h>int main() {pid_t pid = fork(); // 通过fork创建一个新进程 [1]if (pid == 0) {printf("This is the child process.\n"); // 子进程执行的代码 [2]} else if (pid > 0) {printf("This is the parent process.\n"); // 父进程执行的代码 [3]} else {printf("Fork failed.\n"); // fork失败的情况 [4]}return 0;
    }
    
    • [1] 创建新进程fork() 函数用于创建一个新进程。在调用 fork() 之后,父进程和子进程都会执行从 fork() 返回的下一条语句。返回值在两个进程中不同:子进程中 fork() 返回0,而父进程中返回子进程的进程ID。如果返回-1,则表示 fork 创建进程失败。

    • [2] 子进程执行的代码:在子进程中,当 pid 等于 0 时,执行 printf("This is the child process.\n");。这段代码标识当前运行进程为子进程。

    • [3] 父进程执行的代码:在父进程中,当 pid 大于 0 时,执行 printf("This is the parent process.\n");。这段代码标识当前运行进程为父进程,并输出子进程的进程ID。

    • [4] fork失败的情况:如果 fork() 返回 -1,则输出 printf("Fork failed.\n");。这种情况下可能是由于资源受限或者系统限制,导致无法创建新进程。

fork() 的使用常见于多进程程序中,通过创建子进程以并行执行任务。但需要注意不同操作系统的限制和资源分配问题。

  • exec:用一个新程序替换当前进程的地址空间。常见的 exec 系列函数包括 execl, execv, execle, execve 等。它们不会创建新进程,而是 “替换” 当前进程的内容。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main() {char *args[] = { "/bin/ls", NULL };   // 参数数组 [1]execv(args[0], args);                 // 执行'ls'命令 [2]printf("This statement won't be executed if execv is successful.\n");  // 不执行? [3]return 0;
    }
    
    • [1] 参数数组char *args[] = { "/bin/ls", NULL }; 定义了一个参数数组,用于存储将被执行的命令及其参数。这里的 /bin/ls 是 Linux 环境下的一个命令路径,该命令用于列出目录文件。数组的最后一个元素为 NULL,用来标识参数列表的结束。
    • [2] 执行’ls’命令execv(args[0], args); 用于用 args[0] 指定的程序(这里是 /bin/ls)替换当前进程。在成功执行之后,当前程序的代码段、数据段等都将被新的程序加载,因此除非 execv 执行出错,否则 execv 之后的代码将不会再执行。
    • [3] 不执行?printf("This statement won't be executed if execv is successful.\n"); 这行代码在 execv 成功执行后是不会执行的,因为当前程序将被 /bin/ls 的内容替代。如果 execv 失败,比如提供的路径不正确或者没有执行权限,这行代码会输出提示信息。

这个示例程序展示了如何使用 execv来执行另一个程序,并替换当前进程的样例。当需要在程序中执行外部命令时,可以考虑使用此函数或类似的 exec 系列函数 (execl, execvp, execle 等)。

  • exit:终止当前进程,返回状态码供父进程查看。父进程通常使用 wait 系列函数获取子进程的终止状态。

    #include <stdlib.h>int main() {// 一些代码exit(0); // 正常结束程序return 0;
    }
    
1.1.1.3 进程控制(信号、进程间通信)
  • 信号:信号是用于通知进程某个事件发生的机制。常见信号包括 SIGINTSIGKILLSIGTERM 等。进程可以捕捉信号并进行相应处理。

    #include <stdio.h>
    #include <signal.h>// 定义信号捕捉处理函数
    void signal_handler(int signal) {printf("Received signal %d\n", signal); // 打印接收到的信号编号 [1]
    }int main() {signal(SIGINT, signal_handler); // 捕捉 SIGINT 信号,并指定处理函数 [2]while (1); // 无限循环,保持程序运行,等待信号中断 [3]return 0;
    }
    
    • [1] 打印接收到的信号编号:当信号处理函数 signal_handler 被调用时,printf 用于输出接收的信号编号。在此示例中,输出会显示 SIGINT 的默认数值(通常为2)。
    • [2] 捕捉 SIGINT 信号signal(SIGINT, signal_handler); 设置程序对 SIGINT 信号(终端中断信号,通常由 Ctrl+C 生成)的响应动作为调用 signal_handler 函数。signal 函数用来设置信号处理程序,可以用来忽略或捕捉信号。
    • [3] 无限循环while (1); 语句创建了一个无限循环,此处程序将持续运行并等待 SIGINT 信号中断该循环。在这段时间内,程序会停滞在此循环中,用户可以通过发送 SIGINT(Ctrl+C)来触发信号处理函数,从而跳出循环。通常情况下,程序在接收到信号之后会继续到程序结束部分进行处理,但由于这里没有明示的退出机制,程序主要用于演示捕获信号。

这种信号处理机制通常用于需要处理异步事件的程序,以确保程序能响应用户中断、定时事件或其他系统信号。

  • 进程间通信:进程间通信(IPC)有多种方式,包括管道(pipe)、消息队列(message queue)、共享内存(shared memory)、信号量(semaphore)等。它们允许进程间交换数据和信息。
1.1.1.4 进程调度与优先级
  • 进程调度:操作系统根据一定的策略(如时间片轮转、优先级调度)决定将 CPU 分配给哪个进程。调度器是实现这一策略的组件。

  • 进程优先级:在现代操作系统中,进程管理是至关重要的一环,它决定了一个进程何时被系统调度运行以及运行的时间长度。一个重要的管理手段便是通过调整进程的优先级,使得某些重要进程能够获得更多的CPU时间,进而提高系统的整体性能。

这段代码演示了如何使用C语言在Unix系统中管理进程的优先级。

#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h> // 需要包含头文件来支持进程优先级相关操作int main() {// 使用 getpriority 函数获取当前进程的优先级int priority = getpriority(PRIO_PROCESS, getpid());printf("Current priority: %d\n", priority);  // 输出当前优先级 [1]// 使用 nice 函数提升当前进程的优先级int ret = nice(-10);  // 提升优先级 [2]if (ret == -1) perror("nice");  // 检查函数返回值是否有错误 [3]// 再次获取当前进程的优先级以便确认修改priority = getpriority(PRIO_PROCESS, getpid());printf("New priority: %d\n", priority);  // 输出新的优先级 [4]return 0;
}
  • [1] 获取当前进程优先级:调用 getpriority() 函数以PRIO_PROCESS为参数标识进程,通过 getpid() 获取当前进程ID,从而查得该进程的当前优先级。优先级的数值越小,优先级越高。
  • [2] 提升优先级:使用 nice() 函数提升当前进程的优先级,传递 -10 的参数表示尝试减少Nice值(提高优先级)。在Unix系统中,默认情况下,普通用户只能增加Nice值,即降低优先级。
  • [3] 检查函数返回值是否有错误:标准Unix库中,nice() 返回值为 -1 时,通常用 perror() 函数输出错误信息。值得注意的是,提升优先级可能需要更高的权限(如超级用户权限),否则会引发错误。
  • [4] 输出新的优先级:再次调用 getpriority() 确认优先级是否被成功修改,并输出新的值。

理解并适当调整进程的优先级有助于开发高效、稳定的系统应用,特别是在操作需要响应时间或处理能力要求较高的情况下。

1.1.2 内存管理

改进内存管理部分的提纲如下:

1.1.2.1 内存分配策略(动态内存分配,虚拟内存)

内存管理是操作系统和应用程序共同关心的一项关键技术,本节将介绍主要的内存分配策略,包括动态内存分配和虚拟内存。

  • 动态内存分配

    动态内存分配允许程序在运行时分配和释放内存。C语言中,mallocfreecallocrealloc等函数用于动态分配和管理内存。

    #include <stdlib.h>
    #include <stdio.h>int main() {int *arr = (int *)malloc(5 * sizeof(int)); // 动态分配内存 [1]if (arr == NULL) { // 检查 malloc 的返回值 [2]perror("malloc failed");return 1;}for (int i = 0; i < 5; i++) {arr[i] = i * 10; // 赋值使用动态内存 [3]printf("arr[%d] = %d\n", i, arr[i]);}free(arr); // 释放内存 [4]return 0;
    }
    
    • [1] 动态分配内存malloc(5 * sizeof(int)) 分配了一个可以存储5个整数的内存块,并返回指向这块内存的指针。如果分配失败,返回NULL

    • [2] 检查 malloc 的返回值if (arr == NULL) 判断内存分配是否成功。如果失败,arr将为NULL,使用perror输出错误信息。

    • [3] 赋值使用动态内存arr[i] = i * 10 通过指针访问分配的内存,并为每个元素赋值。

    • [4] 释放内存:使用free(arr)释放之前分配的内存,以避免内存泄漏。在分配的内存不再需要使用时,必须显式释放它。

    • 注意事项

      • 检查malloc返回值是否为NULL以防止分配失败。
      • 动态内存分配注意避免内存泄漏,及时使用free释放内存。
      • calloc函数会将分配的内存初始化为0,而malloc不会。
  • 虚拟内存

    虚拟内存通过硬件与操作系统的协作,使每个进程都拥有一个独立的地址空间,从而提高内存利用率和系统稳定性。

    • 页面:虚拟内存被分为固定大小的块,称为页面。
    • 页面表:页面表记录了虚拟地址到物理地址的映射。
    • 缺页:当访问的虚拟地址不存在于物理内存时,会产生缺页中断,操作系统会从磁盘将页面调入内存。
1.1.2.2 页面置换算法

当物理内存已满并需要调入新的页面时,操作系统需决定哪些页面需要被置换出去。常见的页面置换算法有:

  • FIFO(先进先出):最先进入内存的页面最先被置换。
  • LRU(最近最少使用):最近最少使用的页面被置换。
  • LFU(最少使用):使用次数最少的页面被置换。
1.1.2.3 内存映射与共享(mmap

mmap是用于文件与内存映射的系统调用,使得文件内容直接映射到进程的地址空间,从而提高文件访问速度。

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>// 主函数
int main() {int fd = open("example.txt", O_RDONLY);  // 打开文件 [1]if (fd == -1) {                          // 检查文件是否打开成功 [2]perror("open failed");return 1;                            // 打开失败返回 [3]}size_t length = 4096;                    // 假设映射文件大小为4KB [4]char *data = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0); // 内存映射 [5]if (data == MAP_FAILED) {                // 检查映射是否成功 [6]perror("mmap failed");close(fd);                           // 关闭文件描述符 [7]return 1;                            // 映射失败返回 [8]}printf("%s\n", data);                    // 显示文件内容 [9]munmap(data, length);                    // 解除映射 [10]close(fd);                               // 关闭文件描述符 [11]return 0;
}
  • [1] 打开文件:使用 open() 函数以只读模式(O_RDONLY)打开名为 “example.txt” 的文件,并返回文件描述符 fd。如果文件不存在或无法打开,就会返回 -1。

  • [2] 检查文件是否打开成功:判断 open() 函数返回的文件描述符 fd 是否为 -1,以确认文件是否成功打开。

  • [3] 打开失败返回:如果文件打开失败,则输出错误信息并返回错误码 1

  • [4] 假设映射文件大小:设定待映射的内存大小,假设为 4096 字节,即 4KB。

  • [5] 内存映射:使用 mmap() 函数进行内存映射,将文件部分或全部映射到进程地址空间。使用 PROT_READ 表示映射的内存可读,MAP_PRIVATE 表示私有映射。

  • [6] 检查映射是否成功:判断 mmap() 返回的指针是否为 MAP_FAILED 来确认内存映射是否成功。

  • [7] 关闭文件描述符:映射失败时,关闭文件描述符 fd

  • [8] 映射失败返回:若映射失败,输出错误信息并返回错误码 1

  • [9] 显示文件内容:通过 printf("%s\n", data); 输出映射内存中的文件内容。

  • [10] 解除映射:使用 munmap() 函数解除文件至内存的映射,将 data 和对应的长度传入。

  • [11] 关闭文件描述符:操作完毕后,使用 close(fd); 关闭文件描述符 fd

  • 注意事项

    • mmap的返回值需检查是否为MAP_FAILED
    • 使用munmap解除映射以释放资源。
    • 文件映射的内存区域可以实现进程间共享。
1.1.2.4 堆与栈的区别与管理
  • 堆是用于动态内存分配的大块内存区域,程序使用mallocfreerealloc等函数在堆上进行手动内存管理。堆的内存分配和释放需要程序员严格管理,容易导致内存泄漏和分配错误。

  • 栈是用于函数调用的内存区域,包含函数的局部变量、参数和返回地址。栈内存由操作系统自动分配和释放,遇到函数调用时自动分配局部变量,函数返回时自动释放。

#include <stdio.h>
#include <stdlib.h> // 包含 malloc 和 free 的头文件// 定义函数 function
void function() {int local_variable = 10; // 栈上分配的局部变量 [1]
}// 主函数
int main() {int *heap_variable = (int *)malloc(sizeof(int)); // 堆上分配的内存 [2]if (heap_variable == NULL) {perror("malloc failed"); // 错误处理 [3]return 1;}*heap_variable = 20; // 使用动态分配的内存 [4]function(); // 调用 function 函数 [5]free(heap_variable); // 释放堆内存 [6]return 0;
}
  • [1] 栈上分配的局部变量local_variable 是在栈帧内定义的局部变量。当 function 函数被调用时,分配其内存;在函数返回后,内存自动释放。栈内存适用于短暂的、本地的变量分配。

  • [2] 堆上分配的内存:通过 malloc() 函数动态分配内存,返回一个指向分配内存的指针。此处分配了一个 int 类型变量的内存。动态内存分配在堆上进行,堆内存适合于需要在多次函数调用之间持久化的内存。

  • [3] 错误处理:在堆内存分配时,必须检查其返回值是否为 NULL,以确保内存分配成功,如果失败则输出错误信息。

  • [4] 使用动态分配的内存:通过取消引用指针 heap_variable 并赋值 20,使用了动态分配的内存。

  • [5] 调用 function 函数:在 main 中调用 function 函数,执行栈上变量的分配、使用和自动释放。

  • [6] 释放堆内存:在整个使用过程后,通过 free() 函数显式释放在堆上分配的内存,防止内存泄漏。堆内存管理需要程序员自行负责分配和释放。

  • 对比

    • 生命周期:堆内存需要手动管理生命周期,栈内存自动管理。
    • 大小:堆内存可以分配大块内存,栈内存通常较小且有限。
    • 速度:栈内存分配速度较快,堆内存分配较慢因为需要系统管理。

通过学习本节内容,读者应该能够理解C语言中内存分配的基本策略和常见问题,能够在实际项目中有效地进行内存管理,避免内存泄漏和未定义行为。

1.1.3 文件系统

文件系统在操作系统中扮演了极其重要的角色,它管理和组织磁盘上的数据,并提供对文件和目录的访问接口。以下是一些关键的文件系统操作及概念的详细讲解。

1.1.3.1 文件的基本操作(创建,打开,读写,关闭)

在C语言中进行文件操作,需要使用标准库中的文件操作函数。以下是这些操作的详细描述:

  • 创建与打开文件:使用fopen函数来创建和打开文件。
    FILE *fopen(const char *filename, const char *mode);
    
    • filename: 文件名路径。
    • mode:文件打开模式,例如"r"(只读),"w"(写入),"a"(追加)。
#include <stdio.h>int main() {FILE *file = fopen("example.txt", "w");  // [1] 打开文件用于写入if (file == NULL) {                      // [2] 检查文件是否成功打开perror("Error opening file");        // [3] 输出错误信息return 1;                            // [4] 返回错误状态}// 可以在这里对文件进行操作fclose(file);                            // [5] 关闭文件return 0;                                // [6] 程序成功结束
}
  • [1] 打开文件用于写入fopen("example.txt", "w") 尝试以写入模式打开一个名为 example.txt 的文件。如果文件不存在,fopen 会创建一个新文件;如果文件存在,它会清除文件内容。

  • [2] 检查文件是否成功打开:检查返回的文件指针 file 是否为 NULL。如果为 NULL,则表示打开文件失败,可能因为权限不足,路径不对,或者其他原因。

  • [3] 输出错误信息perror 函数用于打印出错误信息,帮助程序员了解程序打开文件失败的原因。这个函数会打印出与前一次函数调用失败相关的错误消息,通常通过标准错误输出流 stderr 打印。

  • [4] 返回错误状态:如果文件未成功打开,程序返回 1 表示异常状态,以区别于正常执行返回的 0

  • [5] 关闭文件:成功完成对文件的操作后,使用 fclose(file) 关闭文件。这是一个良好的做法,以保证释放系统资源,并确保所有缓冲的数据写入到文件中。

  • [6] 程序成功结束:程序正常结束时返回 0,这通常表示程序已成功执行无异常。

通过这一过程,我们看到如何有效地处理文件输入/输出的常用模式,包括资源管理(如关闭文件)和错误处理。这样做有助于保证系统资源的高效使用和程序的稳定性。

  • 读写文件:使用freadfwrite函数进行二进制文件读写。对文本文件,则使用fprintffscanf
    size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
    size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
    int fprintf(FILE *stream, const char *format, ...);
    int fscanf(FILE *stream, const char *format, ...);
    
#include <stdio.h>int main() {FILE *file = fopen("example.txt", "w");  // 打开文件,模式为写入 [1]if (file == NULL) {perror("Error opening file");        // 出错处理 [2]return 1;}fprintf(file, "Hello, World!\n");  // 写入文件 [3]fclose(file);                       // 关闭文件 [4]file = fopen("example.txt", "r");  // 打开文件,模式为读取 [5]if (file == NULL) {perror("Error opening file");  // 出错处理 [6]return 1;}char buffer[100];fgets(buffer, 100, file);          // 读取文件 [7]printf("%s", buffer);              // 打印读取的数据 [8]fclose(file);                      // 关闭文件 [9]return 0;
}
  • [1] 打开文件,模式为写入:使用 fopen() 函数,以写入模式 ("w") 打开名为 "example.txt" 的文件。若文件不存在则创建新文件,若文件存在则清空内容。
  • [2] 出错处理(写入模式):当 fopen() 返回 NULL 时,表示文件打开失败,使用 perror() 打印错误信息。
  • [3] 写入文件:通过 fprintf() 方法向文件写入字符串 "Hello, World!\n"
  • [4] 关闭文件(写入模式):使用 fclose() 关闭已打开的文件,写入操作完成后应及时关闭文件以防止数据丢失。
  • [5] 打开文件,模式为读取:再次用 fopen() 函数以读取模式 ("r") 打开文件 "example.txt"
  • [6] 出错处理(读取模式):同写入模式,再次检查文件是否成功打开。
  • [7] 读取文件:通过 fgets() 函数读取文件中的内容,最多读取 buffer 的大小减一(此处为100字节),保证有空间存储终止符。
  • [8] 打印读取的数据:使用 printf() 将从文件中读取的内容输出到终端。
  • [9] 关闭文件(读取模式):读取操作完成后及时关闭文件。

本示例展示了文件的基本操作,包括如何创建、写入以及读取一个文本文件。正确的文件打开模式和错误处理可以确保程序的稳健性和安全性。

  • 关闭文件:使用fclose来关闭文件,并确保文件指针被释放。
    int fclose(FILE *stream);
    
1.1.3.2 文件描述符与文件指针的区别

在Linux和Unix操作系统中,文件操作可以使用文件描述符或文件指针:

  • 文件描述符(File Descriptor, FD):整数类型,索引内核为每个进程维护的文件表。可以通过openreadwriteclose等系统调用操作文件描述符。
  • 文件指针(File Pointer, FP):高级抽象,FILE *指针类型,通常使用fopenfreadfwritefclose等C标准库函数访问。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>  // 添加头文件以使用 strlen() 函数 [1]int main() {int fd = open("example.txt", O_WRONLY | O_CREAT, 0644);  // 打开或创建文件 [2]if (fd == -1) {perror("Error opening file");  // 错误处理 [3]return 1;}const char *text = "Hello, World!";write(fd, text, strlen(text));  // 写入文件 [4]close(fd);  // 关闭文件 [5]return 0;
}
  • [1] 添加头文件#include <string.h> 用于使用 strlen() 函数计算字符串长度。
  • [2] 打开或创建文件open() 函数打开 example.txt 文件供写操作;如果文件不存在,将创建该文件。标志 O_WRONLY 表示只写模式,O_CREAT 表示如文件不存在则创建文件,权限 0644 即文件所有者可读写,其他用户可读。
  • [3] 错误处理:若 open() 返回值为 -1,则表示打开文件失败,perror() 用于输出错误信息到标准错误输出并返回非零值终止程序。
  • [4] 写入文件write() 函数将 text 中的数据写入打开的文件中,使用 strlen(text) 计算待写入数据的长度。
  • [5] 关闭文件close(fd) 释放文件描述符 fd 以关闭 example.txt 文件,确保写入的数据完整并释放系统资源。
1.1.3.3 文件锁(flock, fcntl

文件锁定是防止多个进程并发访问文件导致数据损坏的重要机制。

  • flock函数:提供排他锁和共享锁。

    #include <sys/file.h>
    int flock(int fd, int operation);
    
  • fcntl函数:更强大的文件锁定机制,支持记录级锁定。

    #include <fcntl.h>
    int fcntl(int fd, int cmd, ...);
    
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/file.h>// 主程序执行文件锁定操作
int main() {int fd = open("example.txt", O_WRONLY); // 以只写模式打开文件 [1]if (fd == -1) { // 检查文件是否成功打开perror("Error opening file");return 1;}if (flock(fd, LOCK_EX) == -1) { // 进行独占锁定 [2]perror("Error locking file");close(fd);return 1;}// 对文件进行操作...// 这些操作处于锁定状态下执行,此时其他进程无法修改文件if (flock(fd, LOCK_UN) == -1) { // 解锁文件 [3]perror("Error unlocking file");}close(fd); // 关闭文件 [4]return 0; // 程序结束
}
  • [1] 以只写模式打开文件open() 函数以 O_WRONLY 标志打开名为 example.txt 的文件,返回一个文件描述符 fd。如果打开失败(返回-1),则输出错误信息并结束程序。
  • [2] 进行独占锁定flock() 函数尝试对文件进行独占锁定(LOCK_EX),即只允许当前进程访问,其他进程必须等待文件解锁。如果锁定失败,程序输出错误信息,关闭文件后结束。
  • [3] 解锁文件flock() 函数解除文件的锁定(LOCK_UN),允许其他进程访问文件。
  • [4] 关闭文件close(fd) 用于关闭文件描述符 fd,释放系统资源。
1.1.3.4 目录操作(创建与删除目录,遍历目录)

可以使用以下函数进行目录操作:

  • 创建目录:使用mkdir创建目录。

    #include <sys/stat.h>
    int mkdir(const char *pathname, mode_t mode);
    
  • 删除目录:使用rmdir删除空目录。

    #include <unistd.h>
    int rmdir(const char *pathname);
    
  • 遍历目录:使用opendir, readdirclosedir函数遍历目录中的文件和子目录。

    #include <dirent.h>
    DIR *opendir(const char *name);
    struct dirent *readdir(DIR *dirp);
    int closedir(DIR *dirp);
    
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>   // 用于目录流操作
#include <sys/stat.h> // 用于文件和文件权限操作
#include <unistd.h>   // 用于访问 POSIX 操作系统 APIint main() {mkdir("new_directory", 0755); // 创建目录 [1]DIR *dir = opendir("."); // 打开当前目录 [2]if (dir == NULL) { // 判断是否成功打开 [3]perror("Error opening directory");return 1;}struct dirent *entry; // 声明目录项结构指针 [4]while ((entry = readdir(dir)) != NULL) { // 读取目录 [5]printf("%s\n", entry->d_name); // 打印目录中的文件名 [6]}closedir(dir); // 关闭目录流 [7]rmdir("new_directory"); // 删除目录 [8]return 0;
}
  • [1] 创建目录mkdir("new_directory", 0755) 创建一个名为 “new_directory” 的新目录,权限设置为 0755,这代表该目录的拥有者可以读写执行,而组和其他用户只有读取和执行权限。

  • [2] 打开当前目录opendir(".") 打开当前工作目录。opendir 返回一个指向 DIR 结构的指针,该对象表示一个目录流。

  • [3] 判断是否成功打开:如果 opendir 返回 NULL,表示目录打开失败,可能原因是没有访问权限或目录不存在。

  • [4] 声明目录项结构指针struct dirent *entry 定义一个指针,用于指向目录流中的条目。

  • [5] 读取目录readdir 返回指向下一个目录条目的指针,读取目录内容直到返回 NULL,表示目录读取完毕。

  • [6] 打印目录中的文件名entry->d_name 是目录项结构的成员,用于存储当前目录项的名称。

  • [7] 关闭目录流closedir(dir) 释放 opendir 所分配的资源。

  • [8] 删除目录rmdir("new_directory") 删除我们之前创建的目录 “new_directory”。注意,rmdir 只能删除空目录。

1.1.3.5 符号链接与硬链接

链接是文件系统中用来建立文件别名的机制。

  • 符号链接:创建指向另一个文件的路径。

    #include <unistd.h>
    int symlink(const char *target, const char *linkpath);
    
  • 硬链接:创建指向相同文件数据的另一个目录项。

    #include <unistd.h>
    int link(const char *oldpath, const char *newpath);
    
#include <stdio.h>
#include <unistd.h>int main() {symlink("example.txt", "example_symlink.txt"); // 创建符号链接 [1]link("example.txt", "example_hardlink.txt");   // 创建硬链接 [2]return 0;
}
  • [1] 创建符号链接symlink("example.txt", "example_symlink.txt"); 使用 symlink 函数创建一个指向 example.txt 的符号链接,命名为 example_symlink.txt。符号链接类似于快捷方式,存储的是目标文件的路径名。

    • 符号链接特性
      • 间接指向:符号链接存储的是文件路径而不是文件数据,因此它可以指向不存在的文件。
      • 跨文件系统:可以在不同的文件系统之间创建符号链接。
      • 占用较少空间:因为只存储了路径信息。
  • [2] 创建硬链接link("example.txt", "example_hardlink.txt"); 使用 link 函数创建一个指向 example.txt 的硬链接,命名为 example_hardlink.txt。硬链接指向与目标文件相同的物理文件数据。

    • 硬链接特性
      • 多指向同一数据:硬链接和链接对象共享相同的物理数据块,任何一个的更改对另一个也会生效。
      • 依存于文件系统:硬链接必须在同一文件系统内,无法跨文件系统。
      • 不可删除的实际数据:即使原始文件被删除,只要有硬链接存在,数据仍然得以保留。
  • 示例说明:这段程序首先创建一个名为 example_symlink.txt 的符号链接,然后创建一个名为 example_hardlink.txt 的硬链接,指向相同的原始文件example.txt。符号链接与硬链接在用途和特性上迥异,符号链接提供更直观的路径指向,而硬链接则实现数据存储的复用效应。

通过以上内容,我们具体了解了文件系统的基本操作。这些知识点在C语言项目开发中尤为重要,可以有效防止数据损坏并提升程序的鲁棒性。

相关文章:

【C语言系统编程】【第一部分:操作系统知识】1.1.操作系统原理

第一部分&#xff1a;操作系统知识 1.1 操作系统原理 1.1.1 进程管理 1.1.1.1 进程的概念与生命周期 进程是程序在计算机中的一次执行实例&#xff0c;包括了程序的代码、数据、以及运行的上下文环境。进程管理是操作系统的核心任务之一。 作用&#xff1a;管理所有执行中…...

基于Java+VUE+echarts大数据智能道路交通信息统计分析管理系统

大数据智能交通管理系统是一种基于Web的系统架构&#xff0c;通过浏览器/服务器&#xff08;B/S&#xff09;模式实现对城市交通数据的高效管理和智能化处理。该系统旨在通过集成各类交通数据&#xff0c;包括但不限于车辆信息、行驶记录、违章情况等&#xff0c;来提升城市管理…...

leetcode-42. 接雨水 单调栈

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表…...

ThinkPHP和PHP的区别

文章目录 ThinkPHP和PHP的区别一、引言二、PHP简介1、第一步1.1、示例代码 三、ThinkPHP简介2、第二步2.1、特点2.2、示例代码 四、总结 ThinkPHP和PHP的区别 一、引言 在Web开发领域&#xff0c;PHP是一种广泛使用的开源脚本语言&#xff0c;而ThinkPHP则是一个基于PHP的MVC…...

clientWidth,offsetWidth,scrollHeight

clientWidth: offsetWidth&#xff1a; scrollHeight&#xff1a;...

SVN版本回退

SVN 版本回退三种方法&#xff1a; Update item to this version 假设我们的项目文件一共有8个版本&#xff0c;它版本号分别是1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#xff0c;7&#xff0c;8。 这个选项的作用是将文件版本更新到对应所选的…...

IDEA关联Tomcat

一、Tomcat服务器 web服务器,就是运行web项目的容器 即运行java代码的一个容器 webapp(web应用程序) --> 就是我们写的javaweb项目 Tomcat 是Apache 软件基金会&#xff08;Apache Software Foundation&#xff09;下的一个核心项目&#xff0c;免费开源、并支持Servlet 和J…...

MongoDB mongoose 的 save、insert 和 create 方法的比较

目录 save 方法 insert 方法 create 方法 使用会话和事务 总结 在本文中&#xff0c;我们将介绍 MongoDB 中使用 mongoose 操作 数据库时的三种常见方法&#xff1a;save、insert 和 create。这些方法可以用于将数据存储到 MongoDB 数据库中&#xff0c;并且在一定程度上具…...

Maven安装使用

说明&#xff1a;Maven是Apache旗下的一个开源项目&#xff0c;是一款用于管理和构建java项目的工具。一般来说&#xff0c;它帮助我们管理依赖、构建项目。本文介绍在Windows系统下安装Maven。 下载&安装&验证 下载 首先&#xff0c;在Maven官网&#xff08;https:…...

微信第三方开放平台接入本地消息事件接口报错问题java.security.InvalidKeyException: Illegal key size

先看报错&#xff1a; java.security.InvalidKeyException: Illegal key sizeat javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1039)at javax.crypto.Cipher.implInit(Cipher.java:805)at javax.crypto.Cipher.chooseProvider(Cipher.java:864)at javax.crypto.Cipher.in…...

如何只修改obsidian图片链接为markdown

如何只修改obsidian图片链接为markdown 前言插件配置 使用注意 前言 适合有一定了解obsidian用法和插件市场&#xff0c;还有相对路径的人 插件 在obsidian插件市场搜索—开梯子 配置 首先使用ctrlp打开命令面板&#xff0c;也可以在左侧通过图标打开命令面板&#xff0c…...

AI不可尽信

看到某项目有类似这样的一段代码 leaves : make([]int, 10) leaves leaves[:0]没理解这样的连续两行,有何作用? 初始化一个长度和容量都为10的切片,接着把切片长度设置为0 即如下demo: (在线地址) package mainimport "fmt"func main() {leaves : make([]int, 1…...

[C++]使用纯opencv部署yolov11旋转框目标检测

【官方框架地址】 GitHub - ultralytics/ultralytics: Ultralytics YOLO11 &#x1f680; 【算法介绍】 YOLOv11是一种先进的对象检测算法&#xff0c;它通过单个神经网络实现了快速的物体检测。其中&#xff0c;旋转框检测是YOLOv11的一项重要特性&#xff0c;它可以有效地检…...

Python入门--函数

目录 1. 函数介绍 2. 函数的定义 3. 函数的参数 4. 函数的返回值 5. 函数说明文档 6. 函数的嵌套调用 7. 函数的作用域 (1). 局部变量 (2). 全局变量 (3). global关键字 1. 函数介绍 函数&#xff1a;是组织好的&#xff0c;可重复使用的&#xff0c;用来实现特定功能…...

winFrom界面无法打开

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&am…...

【Linux】Ubuntu20.04上使用RabbitVCS的图形化SVN

文章目录 1、RabbitVCS1.1、RabbitVCS 介绍1.2、RabbitVCS 主要功能1.3、Ubuntu下 TortoiseSVN 替代者 2、安装2.1、命令安装2.2、安装使用2.3、使用权限 3、解决SVN无法保存密码问题3.1、问题描述3.2、解决方法 1、RabbitVCS 1.1、RabbitVCS 介绍 它是一款Linux系统下的图形…...

DMA直接存储器存取

参考视频&#xff1a;[8-1] DMA直接存储器存取_哔哩哔哩_bilibili DMA简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输&#xff0c;无须CPU干预&#xff0c;节省了CPU的资源 12个独立可…...

java计算机毕设课设—坦克大战游戏

这是什么系统&#xff1f; 坦克大战游戏是一款以坦克为主题的射击游戏&#xff0c;旨在为玩家提供一个刺激、有趣的游戏体验。该游戏不仅拥有丰富的功能&#xff0c;还注重玩家的互动体验。此系统是使用Java语言实现坦克大战游戏程序&#xff0c;玩家通过连接访问进入游戏&…...

Vue入门-指令学习-v-on

v-on 作用&#xff1a;注册事件 添加监听 提供处理逻辑 语法&#xff1a; v-on:事件名"内联语句" v-on:事件名"methods中的函数名" 注意&#xff1a;" v-on&#xff1a;"可以替换为" " v-on:click"XXX" --> cli…...

Maven的生命周期与依赖作用域介绍

说明&#xff1a;本文介绍Maven的生命周期&#xff0c;以及在pom.xml文件中每个依赖&#xff08;dependency标签内&#xff09;scope标签的内容。 Maven生命周期 在IDEA项目中&#xff0c;右侧边栏&#xff0c;点Maven&#xff0c;可以看到以下生命周期。 其中&#xff0c; c…...

Django学习笔记四:urls配置详解

Django是一个高级的Python Web框架&#xff0c;它鼓励快速开发和干净、实用的设计。URL配置是Django框架中非常重要的一部分&#xff0c;它定义了URL模式与视图函数之间的映射关系。以下是Django URL配置的详解&#xff1a; URL配置文件 通常&#xff0c;URL配置位于Django项…...

NIO的callback调用方式

1.消费者 public class CallbackClient {public static void main(String[] args) {try {SocketChannel socketChannel SocketChannel.open();socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));ByteBuffer writeBuffer ByteBuffer.allocate(32);…...

百度文心智能体平台开发萌猫科研加油喵

百度文心智能体平台开发萌猫科研加油喵 在科研的道路上&#xff0c;研究生们常常面临着巨大的压力和挑战。为了给这个充满挑战的群体带来一些鼓励和温暖&#xff0c;我借助百度文心智能体平台开发了一个独特的智能体 《萌猫科研加油喵》。 一、百度文心智能体平台介绍 百度文…...

Hive数仓操作(十六)

DML&#xff08;数据操作语言&#xff09;指的是用于操作数据的 SQL 语言部分&#xff0c;主要包括对数据的插入、更新、删除等操作。Hive 的 DML语句主要包括 INSERT、UPDATE 和 DELETE 。以下是一些重要的 Hive DML 语句及其解析。 Hive的DML语句 一、 插入操作INSERT 一般…...

第十二届蓝桥杯嵌入式省赛程序设计题解析(基于HAL库)(第一套)

一.题目分析 &#xff08;1&#xff09;.题目 &#xff08;2&#xff09;.题目分析 1.串口功能分析 a.串口接收车辆出入信息&#xff1a;通过查询车库的车判断车辆是进入/出去 b.串口输出计费信息&#xff1a;输出编号&#xff0c;时长和费用 c.计算停车时长是难点&#x…...

MongoDB入门:安装及环境变量配置

一、安装MonggoDB Windows系统安装MongoDB 1、下载MongoDB安装包 访问MongoDB官方网站&#xff0c;选择与Windows系统相匹配的MongoDB Community Server版本进行下载。 Download MongoDB Community Server | MongoDB 2、安装MongoDB 双击下载好的安装包文件&#xff0c;根…...

利用 notepad++ 初步净化 HaE Linkfinder 规则所提取的内容(仅留下接口行)

去掉接口的带参部分 \?.*去掉文件行 .*\.(docx|doc|xlsx|xls|txt|xml|html|pdf|ppt|pptx|odt|ods|odp|rtf|md|epub|css|scss|less|sass|styl|png|jpg|jpeg|gif|svg|ico|bmp|tiff|webp|heic|dds|raw|vue|js|ts|mp4|avi|mov|wmv|mkv|flv|webm|mp3|wav|aac|flac|ogg|m4a).*(\r\…...

RCE(remote command/code execute)远程命令注入

远程命令注入RCE RCE(remote command/code execute&#xff0c;远程命令执行)漏洞&#xff0c;一般出现这种漏洞&#xff0c;是因为应用系统从设计上需要给用户提供指定的远程命令操作的接口&#xff0c;比如我们常见的路由器、防火墙、入侵检测等设备的web管理界面上。一般会给…...

​一篇关于密码学的概念性文章

文章目录 1. 引言2. 加密学基本概念3. 加密算法的类型3.1 对称密钥加密(SKC)3.2 公钥密码学3.3 哈希函数3.4. 为什么需要三种加密技术?3.5 密钥长度的重要性4. 信任模型4.1 PGP信任网络4.2 Kerberos4.3 公钥证书和证书颁发机构4.4 总结5. 密码算法的实际应用5.1 密码保护5.2…...

什么是汽车中的SDK?

无论是在家里使用预制菜包做一顿大厨级别的晚餐&#xff0c;还是使用IKEA套组装配出时尚的北欧风桌子&#xff0c;我们都熟悉这样一种概念&#xff1a;比起完全从零开始&#xff0c;使用工具包可以帮助我们更快、更高效地完成一件事。 在速度至关重要的商业软件领域&#xff0…...