【C语言系统编程】【第一部分:操作系统知识】1.3.实践与案例分析

1.3 实践与案例分析
1.3.1 案例分析:实现一个简单的Shell

本节将通过一个简单的Shell程序来展示如何使用C语言中的高级操作系统功能,包括命令行解析、进程管理(forkexec)、管道和重定向。

1.3.1.1 解析命令行输入

在实现Shell时,第一步是解析用户输入的命令行。这一过程包括读取输入、分割命令和参数。

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX_INPUT_SIZE 1024
#define MAX_ARG_SIZE 100// 函数 parse_input:解析用户输入
void parse_input(char *input, char **args) {char *token;token = strtok(input, " \n");  // 使用空格和换行符作为分隔符 [1]int i = 0;while (token != NULL) {args[i++] = token;        // 将分割的片段存入 args 数组 [2]token = strtok(NULL, " \n"); // 获取下一个分割片段 [3]}args[i] = NULL;  // 参数列表以 NULL 结尾 [4]
}int main() {char input[MAX_INPUT_SIZE];char *args[MAX_ARG_SIZE];while (1) {printf("my_shell> ");if (fgets(input, MAX_INPUT_SIZE, stdin) == NULL) {  // 从标准输入读取一行 [5]perror("fgets error");  // 错误处理 [6]exit(1);}parse_input(input, args);  // 解析输入 [7]for (int i = 0; args[i] != NULL; i++) {  // 输出解析完成后的参数 [8]printf("Argument %d: %s\n", i, args[i]); }}return 0;
}
  • [1] 使用空格和换行符作为分隔符strtok 函数用于将 input 中的字符串分割成若干个部分,依据空格和换行符进行分割。
  • [2] 将分割的片段存入 args 数组:分割后的每个子字符串被存放在 args 数组中,args 作为指针数组,每个元素指向一个字符串片段。
  • [3] 获取下一个分割片段:通过在 strtok 函数中传入 NULL,继续获取下一个分割的字符串片段,直到没有更多的分割片段可获取。
  • [4] 参数列表以 NULL 结尾:为了便于后续遍历,通过在 args 数组的末尾加上 NULL 来标记参数列表的结束。
  • [5] 从标准输入读取一行fgets 用于从标准输入流(stdin)中读取一行输入,并存储到 input 数组中。
  • [6] 错误处理:若 fgets 返回 NULL,则表示出现错误,使用 perror 打印错误信息并退出程序。
  • [7] 解析输入:调用 parse_input 函数,将用户输入解析成命令和参数。
  • [8] 输出解析完成后的参数:循环遍历 args 数组,输出每个解析出来的命令或参数。
1.3.1.2 使用forkexec执行命令

在Shell中,用户命令通过创建子进程并使用exec族函数执行。

示例代码

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>// 执行命令函数
void execute_command(char **args) {pid_t pid = fork(); // 创建子进程 [1]if (pid == 0) { // 子进程逻辑if (execvp(args[0], args) == -1) { // 执行命令 [2]perror("exec error"); // 输出执行错误信息exit(1); // 执行失败时退出}} else if (pid < 0) { // fork 创建失败 [3]perror("fork error"); // 输出错误信息} else { // 父进程逻辑wait(NULL); // 等待子进程结束 [4]}
}int main() {char input[MAX_INPUT_SIZE];char *args[MAX_ARG_SIZE];while (1) {printf("my_shell> "); // 输出提示符if (fgets(input, MAX_INPUT_SIZE, stdin) == NULL) { // 获取输入 [5]perror("fgets error"); // 输出读取错误信息exit(1);}parse_input(input, args); // 解析用户输入 [6]if (args[0] == NULL) continue; // 处理空输入 [7]execute_command(args); // 执行命令 [8]}return 0;
}
  • [1] 创建子进程fork() 负责创建一个新的进程,通过返回值区分父进程和子进程。在父进程中,fork() 返回子进程的 PID;在子进程中,返回0;若失败,返回-1。
  • [2] 执行命令execvp() 用于在子进程中运行用户指定的命令。它接收命令和参数数组,如果执行失败,返回 -1。
  • [3] fork失败:发生错误时,fork() 返回负值,并输出错误信息。
  • [4] 等待子进程结束wait(NULL) 是一种阻塞调用,它使父进程等待子进程的完成以确保操作的有序性。
  • [5] 获取输入fgets() 从标准输入读取用户命令,并存储在 input 数组中。
  • [6] 解析用户输入parse_input() 是一个假定已存在的函数,用于将输入的字符串解析为命令和参数。这一函数的具体实现未在示例中提供,需要自行实现。
  • [7] 处理空输入:在解析结果 args 的第一个元素为 NULL 时表示无效输入,程序继续等待下一次输入。
  • [8] 执行命令:通过调用 execute_command() 来实际执行用户输入的命令。
1.3.1.3 管道与重定向的实现

管道和重定向是Shell功能中的重要组成部分。通过管道,可以将一个命令的输出作为下一个命令的输入,而重定向可以将命令的输出重定向到文件中或从文件读取输入。

管道示例代码

#include <unistd.h>    // 包含POSIX API,用于管道、进程控制
#include <sys/types.h> // 定义数据类型,包括`pid_t`
#include <sys/wait.h>  // 用于进程等待
#include <stdio.h>     // 标准输入输出
#include <stdlib.h>    // 标准库函数,包含`exit()`// 函数 execute_pipe:通过管道连接两个命令
void execute_pipe(char **args1, char **args2) {int pipefd[2]; // 用于保存管道的文件描述符 [1]pid_t pid1, pid2;if (pipe(pipefd) == -1) { // 创建管道perror("pipe error");exit(1);}pid1 = fork();if (pid1 == 0) { // 第一个子进程close(pipefd[0]); // 关闭管道读端 [2]dup2(pipefd[1], STDOUT_FILENO); // 将标准输出重定向到管道写端 [3]close(pipefd[1]); // 关闭不再需要的写端if (execvp(args1[0], args1) == -1) { // 执行第一个命令perror("exec error");exit(1);}}pid2 = fork();if (pid2 == 0) { // 第二个子进程close(pipefd[1]); // 关闭管道写端 [4]dup2(pipefd[0], STDIN_FILENO); // 将标准输入重定向到管道读端 [5]close(pipefd[0]); // 关闭不再需要的读端if (execvp(args2[0], args2) == -1) { // 执行第二个命令perror("exec error");exit(1);}}close(pipefd[0]); // 父进程:关闭管道读端 [6]close(pipefd[1]); // 父进程:关闭管道写端 [7]wait(NULL); // 等待第一个子进程完成 [8]wait(NULL); // 等待第二个子进程完成 [9]
}int main() {// 示例:将 `ls` 结果通过管道传递给 `wc -l`char *args1[] = {"ls", NULL}; // 第一个命令参数 [10]char *args2[] = {"wc", "-l", NULL}; // 第二个命令参数 [11]execute_pipe(args1, args2);return 0;
}
  • [1] 管道文件描述符pipefd[2] 创建一个管道,通过数组保存读写端的文件描述符,其中 pipefd[0] 用于读,pipefd[1] 用于写。
  • [2] 关闭读端:在第一个子进程中,我们只需要写端,用于接收命令的输出,因此关闭读端。
  • [3] 重定向输出dup2(pipefd[1], STDOUT_FILENO) 使得标准输出(文件描述符1)指向管道的写端。
  • [4] 关闭写端:在第二个子进程中,我们只需要读端,用于接收另一个命令的输入,因此关闭写端。
  • [5] 重定向输入dup2(pipefd[0], STDIN_FILENO) 使标准输入(文件描述符0)指向管道的读端。
  • [6][7] 父进程关闭管道:父进程应当关闭所有的管道描述符以避免资源浪费。
  • [8][9] 等待子进程:父进程使用 wait(NULL) 函数等待子进程完成,以防止僵尸进程的产生。
  • [10][11] 命令参数char *args1[]char *args2[] 定义了要执行的命令及其参数,用于 execvp() 函数。

此代码演示了基本的进程间通信机制,有助于构建功能更复杂的Shell程序,包括实现命令的解析、执行以及其他类型的输入输出重定向等。

1.3.2 案例分析:实现一个文件系统监控工具

在这部分,我们将讨论如何使用inotify系统调用来监听文件系统事件,并将检测到的事件记录到日志中。inotify是Linux内核提供的一个功能,能够监控文件系统的诸如创建、删除、修改等事件。使用inotify可以帮助我们实现高效的文件系统监控工具。

1.3.2.1 使用系统调用监听文件事件(inotify

inotify是一种强大的机制,可以用来监控文件或目录的变化。以下是使用inotify的基本步骤:

  1. 初始化inotify实例:通过系统调用inotify_initinotify_init1来创建一个新的inotify实例,并返回一个文件描述符。

    #include <sys/inotify.h>
    #include <stdio.h>
    #include <stdlib.h>int inotify_fd = inotify_init(); // 初始化 inotify 实例 [1]
    if (inotify_fd < 0) {perror("inotify_init"); // 错误处理 [2]exit(EXIT_FAILURE);     // 程序退出 [3]
    }
    
    • [1] 初始化 inotify 实例inotify_fd = inotify_init(); 调用 inotify_init() 函数初始化一个 inotify 实例。inotify 是 Linux 内核提供的一个功能,用于监控文件系统事件,比如文件的创建、删除、修改等。返回的文件描述符 inotify_fd 用于后续对文件系统事件的监控操作。

      • 知识点
        • inotify_init() 返回一个文件描述符,它被用于标识 inotify 实例。
        • 如果 inotify_init() 返回-1,则表示初始化失败。
    • [2] 错误处理perror("inotify_init"); 用于处理 inotify_init() 调用失败的情况,打印错误信息 to stderrperror() 函数会输出自定义的错误提示信息和上一个函数调用导致的错误信息(通过检查 errno)。

      • 知识点
        • 检查函数返回值对于检测和处理错误很重要。
        • perror() 是一种处理错误并向用户提供错误源信息的标准方法。
    • [3] 程序退出exit(EXIT_FAILURE);inotify_init() 失败后确保程序安全退出。EXIT_FAILURE 是标准库 cstdlib 中定义的错误退出状态码,通常表示程序异常结束。

      • 知识点
        • 通过 exit() 终止程序并可以返回一个状态码给调用环境。
        • EXIT_FAILUREEXIT_SUCCESS 是标准宏定义,用于表示程序的退出状态,通常用作返回值来指示程序是否正常终止或是遇到错误。
  2. 添加需要监控的文件或目录:使用inotify_add_watch将指定的文件或目录添加到inotify实例中,并指定需要监控的事件。

int wd = inotify_add_watch(inotify_fd, "/path/to/watch", IN_CREATE | IN_DELETE | IN_MODIFY); // 添加监视器 [1]
if (wd < 0) {perror("inotify_add_watch"); // 错误输出 [2]exit(EXIT_FAILURE);          // 退出程序 [3]
}
  • [1] 添加监视器:将路径 "/path/to/watch" 添加到 inotify 的监视列表,并指定感兴趣的事件(IN_CREATEIN_DELETEIN_MODIFY)。
  • [2] 错误输出:通过 perror 函数输出错误信息,当监视器添加失败时,此函数根据 errno 的值输出详细的错误描述。
  • [3] 退出程序:调用 exit(EXIT_FAILURE) 以非零状态退出程序,表示因错误而终止。正常退出状态为零,非零值通常用来表示错误状态。
  1. 读取inotify事件:使用read系统调用,从文件描述符中读取事件。这些事件将会存在一个特定的缓冲区中。

    char buffer[1024] __attribute__ ((aligned(__alignof__(struct inotify_event)))); // [1]
    const struct inotify_event *event; // [2]
    ssize_t len;while (1) {len = read(inotify_fd, buffer, sizeof(buffer)); // [3]if (len < 0) {perror("read");exit(EXIT_FAILURE);}for (char *ptr = buffer; ptr < buffer + len; ptr += sizeof(struct inotify_event) + event->len) { // [4]event = (const struct inotify_event *) ptr; // [5]if (event->len) {printf("File %s was %s\n", event->name, (event->mask & IN_CREATE) ? "created" : // [6](event->mask & IN_DELETE) ? "deleted" : "modified");}}
    }
    
    • [1] 缓冲区对齐和 attributechar buffer[1024] __attribute__ ((aligned(__alignof__(struct inotify_event)))); 这里使用了 __attribute__ 指令来确保 buffer 按照 struct inotify_event 的对齐方式对齐。这是为了满足特定架构对内存对齐的要求,从而提高性能或避免错误。

    • [2] inotify_event 结构体指针const struct inotify_event *event; 声明了一个指针,用于指向在 buffer 中读取的事件。

    • [3] 读取事件信息read(inotify_fd, buffer, sizeof(buffer)); 通过 read 函数从 inotify 文件描述符 inotify_fd 中读取事件数据,存储在 buffer 中。

    • [4] 遍历所有事件:通过一个循环遍历 buffer 中的所有事件。ptrbuffer 开始,步进大小是每个 struct inotify_event 的大小加上 event->len(即事件名的长度),逐个读取事件并处理。

    • [5] 事件指针调整event = (const struct inotify_event *) ptr; 将当前指针 ptr 所指的数据区段转换为 struct inotify_event 结构体进行处理。

    • [6] 事件检测与输出printf("File %s was %s\n", event->name, ...); 通过检查 event->mask 中的标志位,决定文件是被创建、删除还是修改,并打印对应信息。IN_CREATEIN_DELETEIN_MODIFY 是 inotify 事件中常用的掩码宏定义用于表示不同的文件系统事件。

1.3.2.2 日志记录与分析

为了更好地跟踪和分析文件系统变化,我们可以将这些事件记录到日志文件中。以下是一个简单的日志记录实现:

FILE *log_file = fopen("file_system_monitor.log", "a");
if (log_file == NULL) {perror("fopen");exit(EXIT_FAILURE);
}while (1) {len = read(inotify_fd, buffer, sizeof(buffer));if (len < 0) {perror("read");exit(EXIT_FAILURE);}for (char *ptr = buffer; ptr < buffer + len; ptr += sizeof(struct inotify_event) + event->len) {event = (const struct inotify_event *) ptr;if (event->len) {char *event_type = (event->mask & IN_CREATE) ? "created" : (event->mask & IN_DELETE) ? "deleted" : "modified";fprintf(log_file, "File '%s' was %s\n", event->name, event_type);fflush(log_file);}}
}
  • [1] 初始化 inotify 实例:首先需要使用inotify_init()函数创建一个新的inotify实例,该实例返回一个文件描述符 inotify_fd,用于监听文件系统事件。

  • [2] 添加监控:通过inotify_add_watch()函数,将需要监控的目录或文件加入到inotify实例中,同时指定要监听的事件类型,比如文件的创建(IN_CREATE)、删除(IN_DELETE)、修改(IN_MODIFY)等事件。一旦这些事件在指定目录或文件上发生,inotify将报告这些事件。

  • [3] 读取事件:使用read()系统调用从inotify_fd读取已经触发的事件。读取的数据存储在缓冲区中,其中一个事件数据包含多个struct inotify_event结构,需逐个分析。

  • [4] 事件日志记录:对于每一个获取到的inotify_event事件,检查其事件类型(新建、删除、修改),然后使用fprintf()将事件写入日志文件中 file_system_monitor.log。执行 fflush(log_file) 是为了确保数据立刻被写入文件,而不是缓存在内存中,以便实时分析。

实现文件系统监控

通过这样的实现,您可以构建一个简单但有效的文件系统监控工具,实时监听文件或目录的变化,并记录这些事件至日志文件,以便后续进行详细的分析。这种机制对于检测未经授权的文件访问、审计文件操作活动以及其它与文件系统有关的监控应用非常有用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/55792.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java | Leetcode Java题解之第461题汉明距离

题目&#xff1a; 题解&#xff1a; class Solution {public int hammingDistance(int x, int y) {int s x ^ y, ret 0;while (s ! 0) {s & s - 1;ret;}return ret;} }

图论day56|广度优先搜索理论基础 、bfs与dfs的对比(思维导图)、 99.岛屿数量(卡码网)、100.岛屿的最大面积(卡码网)

图论day56|广度优先搜索理论基础 、bfs与dfs的对比&#xff08;思维导图&#xff09;、 99.岛屿数量&#xff08;卡码网&#xff09;、100.岛屿的最大面积&#xff08;卡码网&#xff09;&#xff09; 广度优先搜索理论基础bfs与dfs的对比&#xff08;思维导图&#xff09;&…

音视频入门基础:FLV专题(12)——FFmpeg源码中,解析DOUBLE类型的ScriptDataValue的实现

一、引言 从《音视频入门基础&#xff1a;FLV专题&#xff08;9&#xff09;——Script Tag简介》中可以知道&#xff0c;根据《video_file_format_spec_v10_1.pdf》第80到81页&#xff0c;SCRIPTDATAVALUE类型由一个8位&#xff08;1字节&#xff09;的Type和一个ScriptDataV…

电影选票选座系统|影院购票|电影院订票选座小程序|基于微信小程序的电影院购票系统设计与实现(源码+数据库+文档)

电影院订票选座小程序 目录 基于微信小程序的电影院购票系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户功能实现 2、管理员功能实现 &#xff08;1&#xff09;影院信息管理 &#xff08;2&#xff09;电影信息管理 &#xff08;3&#xff09;已完成…

Linux 环境chrony设置服务器间时间同步一致

服务器&#xff1a; master01: slave02: slave03: 安装chrony安装&#xff1a; yum -y install chrony 设置以master01为时间服务器&#xff0c;其他服务器同步master01时间 master01的chrony.conf配置: server ntp1.aliyun.com iburst allow all local stratum 10重启ch…

VUE 开发——Vue学习(二)

一、watch侦听器 作用&#xff1a;监视数据变化&#xff0c;执行一些业务逻辑或异步操作 简单写法 <div id"app"><textarea v-model"words"></textarea></div><script>const app new Vue({el:#app,data: {words: },watch…

在北京能不能设计一款可以多屏展示的调度桌

在北京这座科技与创新并蓄的国际大都市&#xff0c;设计一款集高效、智能与多屏展示功能于一体的调度桌&#xff0c;不仅是一个技术挑战&#xff0c;更是对未来工作场景的一次深刻探索与重塑。那么&#xff0c;在北京能不能设计一款可以多屏展示的调度桌呢? 随着信息技术的飞速…

AI产品经理指南:我是谁,从哪来,到哪去|对谈字节AI产品负责人Vanessa,面试了100位AI产品经理后的心得总结

AI 正在改变各行各业&#xff0c;或许首当其冲受到影响的就包括离 AI 最近的一群人——产品经理。 Vanessa 在字节负责 AI 产品工作&#xff0c;我们从「面试了 100 位 AI 产品经理」的心得总结开始&#xff0c;聊了聊究竟什么是「AI 产品经理」&#xff1f;Vanessa资深的产品经…

docker-compose查看容器日志和实时查看日志

要查看 docker-compose up 过程中容器启动的错误日志&#xff0c;可以使用以下方法&#xff1a; ### 1. **使用 docker-compose logs 命令** 1. 在终端中进入包含 docker-compose.yml 文件的目录。 2. 运行以下命令来查看所有容器的日志&#xff1a; bash docker-compose…

QT元对象系统特性详细介绍(信号槽、类型信息、动态设置属性)(注释)

目录 一、元对象系统简介 二、信号和槽 三、类型信息 四、动态设置属性 一、元对象系统简介 QT中的元对象系统Q_OBJECT并不是C标准代码&#xff0c;因此在使用时需要QT的MOC&#xff08;元对象编译器&#xff09;进行预处理&#xff0c;MOC会在编译时期读取C代码中的特定宏…

java反序列化之CommonCollections6利⽤链的学习

一、源起 前文学习CC1链和URLDNS链的学习&#xff0c;同时学习过程中知道cc1受jdk版本的限制&#xff0c;故而进一步分析cc6链的利用过程&#xff0c;这个利用链不受jdk版本的限制&#xff0c;只要commons collections小于等于3.2.1&#xff0c;都存在这个漏洞。 ps&#xff1…

运用MinIO技术服务器实现文件上传——在Linux系统上安装和启动(一)

# MinIO 单机版环境搭建详解 ## 1. 简介 随着大数据时代的到来&#xff0c;数据存储的需求日益增大&#xff0c;如何有效地存储和管理大规模的非结构化数据成为许多企业和开发者面临的挑战。MinIO 作为一个高性能、分布式对象存储系统&#xff0c;致力于为用户提供简单、快速…

Linux 6.11版本发布

Linux 6.11版本的发布是Linux社区的一个重要里程碑&#xff0c;它不仅在实时计算、性能优化方面取得了显著进展&#xff0c;还在安全性上迈出了关键一步。 一、实时计算与性能优化 1.io_uring子系统支持 Linux 6.11引入了io_uring子系统的增强功能&#xff0c;特别是支持了b…

SpringBoot中间件Docker

Docker&#xff08;属于C/S架构软件&#xff09; 简介与概述 1.Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux …

【ubuntu】Ubuntu20.04安装中文百度输入法

1.download 百度Linux输入法-支持全拼、双拼、五笔 2.unzip unzip Ubuntu_Deepin-fcitx-baidupinyin-64.zip 3.setting 3.1 setting fcitx sudo apt install aptitude sudo aptitude install fcitx-bin fcitx-table fcitx-config-gtk fcitx-frontend-all sudo aptitude in…

Cocos_鼠标滚轮放缩地图

文章目录 前言一、环境二、版本一_code2.分析类属性方法详细分析详细分析onLoad()onMouseWheel(event)详细分析 总结 前言 学习笔记&#xff0c;请多多斧正。 一、环境 通过精灵rect放置脚本实现鼠标滚轮放缩地图。 二、版本一_code import { _decorator, Component, Node }…

使用Buildpacks构建Docker镜像

## 使用Buildpacks构建Docker镜像 ![](../assets/运维手册-Buildpacks-Buildpacks.io.png) ### Buildpacks简介 与Dockerfile相比&#xff0c;Buildpacks为构建应用程序提供了更高层次的抽象。具体来说&#xff0c;Buildpacks&#xff1a; * 提供一个平衡的控制&#xff0c;…

【Python】Conda离线执行命令

以下链接证明了想要离线使用conda命令的方法 启用离线模式 — Anaconda documentation 基本上大部分的命令都会提供网络选项 例如creat命令 conda create — conda 24.7.1 文档 - Conda 文档

多区域OSPF路由协议

前言 之前也有过关于OSPF路由协议的博客&#xff0c;但都不是很满意&#xff0c;不是很完整。现在也是听老师讲解完OSPF路由协议&#xff0c;感触良多&#xff0c;所以这里重新整理一遍。这次应该是会满意的 一些相关概念 链路状态 链路指路由器上的一个接口&#xff0c;链路状…

ubuntu的useradd和adduser命令

useradd vs adduser 1. useradd 类型&#xff1a;Linux 命令。功能&#xff1a;用于创建新用户&#xff0c;但不会自动创建用户的主目录和密码。参数&#xff1a; -c&#xff1a;添加备注。-d&#xff1a;指定用户主目录。-e&#xff1a;设置用户有效期。-f&#xff1a;设置密…