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

第九周预习报告

文章目录

  • 第九周预习报告
    • 学习内容
    • AI 对学习内容的总结
      • 章节概述
      • 内容总结
    • 对知识总结的反思
    • 思维导图
    • 基于AI的学习
      • 知识点1:系统调用
      • 知识点2:进程控制
    • 学习实践过程遇到的问题与解决方式
      • 问题1:`fork()` 调用后,子进程和父进程同时运行导致输出混乱
      • 问题2:`exec()` 调用失败,程序无法执行新程序
      • 问题3:`open()` 系统调用失败,无法打开文件
      • 问题4:`wait()` 调用后,父进程无法正确捕获子进程的退出状态
      • 问题5:环境变量传递失败,子进程无法获取正确的环境变量
    • 参考资料

第九周预习报告

学习内容

  • Head First C 第9章

AI 对学习内容的总结

章节概述

  • 本章主要介绍进程和系统调用的概念,学习如何在自己的代码内部使用其他程序,学会如何使用系统服务来创建和控制进程。使程序能够访问电子邮件、Web以及其他任何安装的工具。

内容总结

  • 系统调用

    • 定义:系统调用是用户程序与操作系统内核之间的接口,允许用户程序请求操作系统执行特定的任务。
    • 常见系统调用
      • open():打开文件。
      • read():从文件中读取数据。
      • write():将数据写入文件。
      • close():关闭文件。
      • fork():创建一个新进程。
      • exec():执行一个新程序。
      • wait():等待子进程结束。
      • exit():终止当前进程。
    • 工作原理:当用户程序调用一个系统调用时,CPU会从用户模式切换到内核模式,内核执行相应的操作,完成后返回结果,CPU再切换回用户模式。
  • 进程控制

    • 进程:进程是操作系统进行资源分配和调度的基本单位,每个进程都有自己的地址空间、文件描述符、环境变量等。
    • 创建进程
      • fork():创建一个新进程,新进程是调用进程的副本,两者几乎完全相同,但有一个唯一的区别:fork()返回值不同。在父进程中返回子进程的PID,在子进程中返回0。
    • 执行新程序
      • exec():有一系列函数(如execl()execlp()execle()execv()execvp()execvpe()),用于替换当前进程的地址空间,使其开始执行一个新程序。
    • 等待子进程
      • wait():父进程调用wait()等待子进程结束,wait()会阻塞父进程直到子进程结束。
    • 终止进程
      • exit():终止当前进程,可以传递一个退出状态码给父进程。
  • 环境变量传递

    • 定义:环境变量是操作系统提供的一种机制,用于在进程之间传递配置信息。
    • 传递方式:子进程会继承父进程的环境变量,可以通过exec()系列函数传递新的环境变量。
  • 错误处理

    • 返回值:大多数系统调用都有一个返回值,成功时返回一个有意义的值,失败时返回一个错误码(通常是负数)。
    • errno:全局变量errno用于存储系统调用的错误代码,可以通过strerror()函数将错误代码转换为人类可读的字符串。
      #include <stdio.h>
      #include <unistd.h>
      #include <errno.h>
      #include <string.h>int main() {int fd = open("nonexistent_file.txt", O_RDONLY);if (fd == -1) {fprintf(stderr, "Error opening file: %s\n", strerror(errno));return 1;}close(fd);return 0;
      }
      
  • RSS订阅

    • 定义:RSS(Really Simple Syndication)是一种用于发布经常更新的内容的标准格式,如博客文章、新闻等。
    • 实现:通过系统调用读取RSS源,解析XML数据,提取所需信息。
      #include <stdio.h>
      #include <curl/curl.h>size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream) {size_t written = fwrite(ptr, size, nmemb, stream);return written;
      }int main() {CURL *curl;FILE *file;CURLcode res;curl_global_init(CURL_GLOBAL_DEFAULT);curl = curl_easy_init();if(curl) {file = fopen("rss_feed.xml", "wb");curl_easy_setopt(curl, CURLOPT_URL, "http://example.com/rss.xml");curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);res = curl_easy_perform(curl);if(res != CURLE_OK) {fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));}fclose(file);curl_easy_cleanup(curl);}curl_global_cleanup();return 0;
      }
      
  • 父子进程关系

    • 父子关系:通过fork()创建的子进程是父进程的子进程,子进程可以调用getppid()获取父进程的PID。
      #include <stdio.h>
      #include <unistd.h>
      #include <sys/wait.h>int main() {pid_t pid = fork();if (pid < 0) {fprintf(stderr, "Fork failed\n");return 1;} else if (pid == 0) {// 子进程printf("Child process: PID = %d, PPID = %d\n", getpid(), getppid());execlp("/bin/ls", "ls", "-l", NULL);} else {// 父进程printf("Parent process: PID = %d, Child PID = %d\n", getpid(), pid);wait(NULL);  // 等待子进程结束printf("Child process finished\n");}return 0;
      }
      

对知识总结的反思

  • 系统调用

    • 系统调用是用户程序与操作系统内核之间的桥梁,通过系统调用可以执行各种底层操作,如文件操作、进程控制等。
    • 在实际编程中,需要仔细检查每个系统调用的返回值,以确保调用成功,并处理可能出现的错误。
  • 进程控制

    • 进程控制是操作系统管理进程生命周期的关键部分,通过fork()exec()可以创建和执行新的进程。
    • 在使用fork()exec()时,需要注意父子进程的关系,确保程序逻辑的正确性。
  • 环境变量传递

    • 环境变量是进程之间传递配置信息的重要机制,子进程会继承父进程的环境变量。
    • 在调用exec()系列函数时,可以通过参数传递新的环境变量。
  • 错误处理

    • 错误处理是确保程序健壮性的关键,通过检查系统调用的返回值和使用errno,可以有效地处理错误。
  • RSS订阅

    • RSS订阅是一个实际应用的例子,展示了如何使用系统调用和网络编程来获取和处理外部数据。
    • 通过CURL库,可以方便地实现HTTP请求和数据下载。

思维导图

mindmap本周学习内容进程和系统调用章节概述系统调用定义常见系统调用openreadwritecloseforkexecwaitexit进程控制进程创建进程fork执行新程序exec等待子进程wait终止进程exit环境变量传递定义传递方式错误处理返回值errnoRSS订阅定义实现使用CURL库解析XML数据父子进程关系父子关系getppid重点内容系统调用定义常见系统调用openreadwritecloseforkexecwaitexit进程控制进程创建进程fork执行新程序exec等待子进程wait终止进程exit环境变量传递定义传递方式错误处理返回值errnoRSS订阅定义实现使用CURL库解析XML数据父子进程关系父子关系getppid

在这里插入图片描述

基于AI的学习

知识点1:系统调用

AI: 举一个常见的系统调用例子。

: 打开文件是一个常见的系统调用。当程序想要读取或写入文件时,它会调用 open() 系统调用。这个调用会请求操作系统打开指定的文件,并返回一个文件描述符,程序可以使用这个文件描述符进行后续的读写操作。

AI: 系统调用 fork()exec() 有什么区别?

: fork()exec() 是两个不同的系统调用,它们分别用于不同的目的。

  • fork() 用于创建一个新的进程。新进程是调用进程的副本,具有相同的代码段、数据段和堆栈段,但有自己的独立内存空间。fork() 返回一个值,如果返回值为0,则表示当前进程是子进程;如果返回值大于0,则表示当前进程是父进程,返回值是子进程的PID。
  • exec() 用于在当前进程中加载并运行一个新的程序。exec() 有多个变种,如 execl()execlp()execle()execv()execvp()execvpe(),它们的主要区别在于参数的传递方式。调用 exec() 之后,当前进程的代码段、数据段和堆栈段会被新程序的代码段、数据段和堆栈段替换。

AI: wait() 系统调用的作用是什么?

: wait() 系统调用用于父进程等待子进程结束。父进程调用 wait() 会阻塞,直到一个子进程结束。wait() 返回子进程的PID,并且可以通过返回值和 wait() 的参数来获取子进程的退出状态。

AI: 如何处理系统调用的错误?

: 处理系统调用的错误通常涉及以下几个步骤:

  1. 检查系统调用的返回值。大多数系统调用在成功时返回一个有意义的值,而在失败时返回一个错误码(通常是负数)。
  2. 如果系统调用失败,检查全局变量 errno 以获取具体的错误代码。
  3. 使用 strerror() 函数将错误代码转换为人类可读的错误信息,并进行适当的错误处理,如打印错误信息、记录日志或采取其他补救措施。

AI: 举一个处理 open() 系统调用错误的例子。

: 当然,以下是一个处理 open() 系统调用错误的示例代码:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>int main() {int fd = open("nonexistent_file.txt", O_RDONLY);if (fd == -1) {fprintf(stderr, "Error opening file: %s\n", strerror(errno));return 1;}close(fd);return 0;
}

在这个例子中,如果 open() 调用失败,fd 将被设置为 -1,程序会检查 errno 并使用 strerror() 函数将错误代码转换为错误信息,然后打印错误信息并返回1表示程序异常退出。

知识点2:进程控制

AI: 什么是进程?

: 进程是操作系统进行资源分配和调度的基本单位。每个进程都有自己的地址空间、文件描述符、环境变量等。进程可以包含一个或多个线程,每个线程共享进程的资源。

AI: fork() 系统调用的返回值有什么含义?

: fork() 系统调用的返回值有三种可能:

  • 如果 fork() 成功,它会在父进程中返回子进程的PID(进程标识符),在子进程中返回0。
  • 如果 fork() 失败,它会返回-1,并设置全局变量 errno 以指示具体的错误原因。

AI: exec() 系统调用的变种有哪些?

: exec() 系统调用有多个变种,它们的主要区别在于参数的传递方式:

  • execl(): 以固定数量的参数调用新程序。
  • execlp(): 类似于 execl(),但会在环境变量 PATH 中查找可执行文件。
  • execle(): 类似于 execl(),但允许传递环境变量。
  • execv(): 以数组形式传递参数。
  • execvp(): 类似于 execv(),但会在环境变量 PATH 中查找可执行文件。
  • execvpe(): 类似于 execvp(),但允许传递环境变量。

AI: wait() 系统调用的参数和返回值是什么?

: wait() 系统调用的原型如下:

pid_t wait(int *status);
  • 参数 status 是一个指向整数的指针,用于接收子进程的退出状态。如果不需要获取退出状态,可以将 status 设为 NULL
  • 返回值是子进程的PID。如果没有子进程结束,wait() 会阻塞父进程。如果 wait() 失败,它会返回-1,并设置全局变量 errno 以指示具体的错误原因。

AI: 举一个使用 fork()exec() 的示例。

: 当然,以下是一个使用 fork()exec() 的示例代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid < 0) {fprintf(stderr, "Fork failed\n");return 1;} else if (pid == 0) {// 子进程printf("Child process: PID = %d, PPID = %d\n", getpid(), getppid());execlp("/bin/ls", "ls", "-l", NULL);fprintf(stderr, "Failed to execute ls\n");return 1;} else {// 父进程printf("Parent process: PID = %d, Child PID = %d\n", getpid(), pid);wait(NULL);  // 等待子进程结束printf("Child process finished\n");}return 0;
}

在这个例子中,父进程调用 fork() 创建一个子进程。子进程调用 execlp() 执行 /bin/ls 命令,列出当前目录的详细信息。父进程调用 wait() 等待子进程结束,然后打印一条消息表示子进程已经结束。

学习实践过程遇到的问题与解决方式

问题1:fork() 调用后,子进程和父进程同时运行导致输出混乱

描述:在使用 fork() 创建子进程后,子进程和父进程同时运行,导致输出混乱,难以区分哪个输出来自哪个进程。
解决方式

  1. 使用 sleep() 临时解决
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>int main() {pid_t pid = fork();if (pid < 0) {fprintf(stderr, "Fork failed\n");return 1;} else if (pid == 0) {// 子进程sleep(1);  // 让子进程稍微延迟一下printf("Child process: PID = %d, PPID = %d\n", getpid(), getppid());execlp("/bin/ls", "ls", "-l", NULL);} else {// 父进程printf("Parent process: PID = %d, Child PID = %d\n", getpid(), pid);wait(NULL);  // 等待子进程结束printf("Child process finished\n");}return 0;
    }
    
  2. 使用 wait() 确保子进程先结束
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>int main() {pid_t pid = fork();if (pid < 0) {fprintf(stderr, "Fork failed\n");return 1;} else if (pid == 0) {// 子进程printf("Child process: PID = %d, PPID = %d\n", getpid(), getppid());execlp("/bin/ls", "ls", "-l", NULL);} else {// 父进程wait(NULL);  // 等待子进程结束printf("Parent process: PID = %d, Child PID = %d\n", getpid(), pid);printf("Child process finished\n");}return 0;
    }
    

问题2:exec() 调用失败,程序无法执行新程序

描述:在调用 exec() 系列函数时,程序无法成功执行新程序,导致程序挂起或报错。
解决方式

  1. 检查路径和文件名
    确保提供的路径和文件名是正确的。如果使用 execlp()execvp(),确保环境变量 PATH 中包含了可执行文件的路径。
  2. 检查错误信息
    使用 perror()strerror(errno) 打印详细的错误信息。
    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>int main() {pid_t pid = fork();if (pid < 0) {fprintf(stderr, "Fork failed\n");return 1;} else if (pid == 0) {// 子进程execlp("/bin/ls", "ls", "-l", NULL);perror("Failed to execute ls");  // 打印错误信息return 1;} else {// 父进程wait(NULL);  // 等待子进程结束printf("Child process finished\n");}return 0;
    }
    

问题3:open() 系统调用失败,无法打开文件

描述:在调用 open() 系统调用时,无法打开指定的文件,导致程序出错。
解决方式

  1. 检查文件路径和权限
    确保文件路径是正确的,并且当前用户有权限访问该文件。
  2. 检查错误信息
    使用 perror()strerror(errno) 打印详细的错误信息。
    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>int main() {int fd = open("nonexistent_file.txt", O_RDONLY);if (fd == -1) {fprintf(stderr, "Error opening file: %s\n", strerror(errno));return 1;}close(fd);return 0;
    }
    

问题4:wait() 调用后,父进程无法正确捕获子进程的退出状态

描述:在调用 wait() 系统调用后,父进程无法正确捕获子进程的退出状态,导致程序逻辑出错。
解决方式
使用 WIFEXITEDWEXITSTATUS
使用 WIFEXITED 检查子进程是否正常退出,使用 WEXITSTATUS 获取子进程的退出状态。

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid < 0) {fprintf(stderr, "Fork failed\n");return 1;} else if (pid == 0) {// 子进程execlp("/bin/ls", "ls", "-l", NULL);perror("Failed to execute ls");return 1;} else {// 父进程int status;pid_t child_pid = wait(&status);if (WIFEXITED(status)) {printf("Child process %d exited with status %d\n", child_pid, WEXITSTATUS(status));} else {printf("Child process %d terminated abnormally\n", child_pid);}}return 0;
}

问题5:环境变量传递失败,子进程无法获取正确的环境变量

描述:在使用 exec() 系列函数时,子进程无法获取正确的环境变量,导致程序出错。
解决方式
检查环境变量的传递方式
确保在调用 exec() 系列函数时正确传递了环境变量。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main() {pid_t pid = fork();if (pid < 0) {fprintf(stderr, "Fork failed\n");return 1;} else if (pid == 0) {// 子进程char *new_env[] = {"PATH=/usr/bin:/bin", "MY_VAR=value", NULL};execle("/bin/ls", "ls", "-l", NULL, new_env);perror("Failed to execute ls");return 1;} else {// 父进程wait(NULL);  // 等待子进程结束printf("Child process finished\n");}return 0;
}

参考资料

  • HeadFirstC嗨翻C语言
  • 通义千问
  • Linux System Calls
  • Process Control in Linux
  • Error Handling in C
  • 课程 mindmap
  • Mermaid Live Editor
http://www.lryc.cn/news/479820.html

相关文章:

  • 【分享】这篇教程助力你成为 JavaScript 糕手!(四)
  • 双亲委派模型的破坏
  • Android关机流程知多少?
  • 深入理解指针end(总结篇)
  • C# 程序暂停的两种方式
  • 【LeetCode】【算法】160.相交链表
  • 光伏破局 引领能源革命
  • Jenkins声明式Pipeline流水线语法示例
  • 互联网技术净土?原生鸿蒙开启全新技术征程
  • 关于Django 模型字段 `choices`自定义数据类型的枚举——补充
  • CAP理论的延申--BASE理论
  • 【傻呱呱】phpMyAdmin怎样给特定用户授权特定数据库权限?
  • 『VUE』21. 组件注册(详细图文注释)
  • 如何产看SQL 查询的执行时间
  • 计算机网络——路由器构成
  • 架构师之路-学渣到学霸历程-48
  • HappyChart——一款简单好用的专业绘图软件
  • 【Linux】进程信号全攻略(二)
  • redis用法(二)
  • Python-利用os,tkinter库编写一个伪恶意程序文件(Pro版)
  • Oracle视频基础1.4.4练习
  • GOF的C++软件设计模式的分类和模式名称
  • 有向图的完全可达性(有向图搜索全路径的问题) C#DFs
  • 前端开发实现自定义勾选/自定义样式,可复选,可取消勾选
  • 鸿蒙-promptAction.showToast基于PC屏幕底部提示
  • Vert.x,应用监控 - 全链路跟踪,基于Zipkin
  • Rust常用数据结构教程 序列
  • 智慧城市路面垃圾识别系统产品介绍方案
  • 网络安全:构建坚固的数字堡垒
  • LeetCode题练习与总结:打乱数组--384