家居企业网站建设服务,app开发公司 杭州,四平网站建设营销,阿柳云wordpress异常控制流
异常控制流出现的地方#xff1a; 异常控制流#xff08;Exceptional Control Flow#xff0c;ECF#xff09;是程序执行过程中由于某些特殊事件或条件而导致的控制流的改变。异常控制流通常出现在以下几种情况#xff1a;
硬件异常和中断#xff1a;硬件异…异常控制流
异常控制流出现的地方 异常控制流Exceptional Control FlowECF是程序执行过程中由于某些特殊事件或条件而导致的控制流的改变。异常控制流通常出现在以下几种情况
硬件异常和中断硬件异常是由处理器检测到的潜在错误条件例如除以零、访问非法内存地址或浮点溢出。硬件中断是由外部设备如鼠标、键盘或网络接口卡发出的信号通知处理器有待处理的事件。在这些情况下处理器会暂停当前执行的指令序列转而执行一个预先定义好的异常处理程序或中断处理程序。操作系统内核操作系统内核在响应系统调用时可能会改变控制流。例如一个进程发出读取文件的系统调用时操作系统可能需要将控制流从用户进程切换到内核态处理文件读取请求然后再将控制流切换回用户进程。信号Signal信号是一种软件异常控制流用于在进程间传递通知。当一个进程接收到信号时操作系统会中断当前进程的执行转而执行与信号关联的信号处理函数。信号可以由其他进程发送也可以由操作系统生成例如当一个进程执行非法操作时如访问非法内存地址。异常处理语句在高级编程语言中异常控制流通常表现为异常处理语句如 try-catch-finally 语句。程序员可以使用这些语句来捕获和处理运行时可能发生的异常情况例如文件读取错误、空指针引用或网络连接中断。线程同步和调度在多线程环境中线程调度和同步也可能导致异常控制流。当一个线程等待另一个线程释放互斥锁或资源时线程调度器可能会将控制流切换到其他线程直到资源可用。这种情况下异常控制流体现在线程的切换与同步。 异常控制流解决问题 异常控制流Exception Control Flow是一种程序执行过程中对异常事件进行处理的机制。异常事件是程序在运行过程中可能遇到的不寻常或非预期的情况例如除以零、数组越界、文件读取错误等。为了确保程序能够更稳定地运行开发者需要通过异常控制流来检测和处理这些异常情况。 异常控制流解决问题的优势 (1)提高程序的稳定性通过捕获和处理异常程序可以在发生错误时继续运行而不是直接崩溃。 (2)提高程序的可维护性集中处理异常使得代码更易于阅读和维护有助于发现潜在的问题。 (3)提高用户体验异常处理可以让程序在发生错误时给用户提供更友好和详细的错误信息。
异常的定义 异常通常指与正常情况不同的、意外的或不寻常的情况或事件。在计算机编程中异常通常是指程序运行时发生的错误或意外情况例如系统崩溃、数据丢失或输入错误。 异常表
异常表指的是在计算机程序运行过程中可能会发生的异常情况以及其对应的处理方式的一张表格。异常通常包括以下几个方面的内容
异常类型列出可能发生的异常类型如空指针异常、数组越界异常、文件未找到异常等异常信息对每种异常类型进行详细描述包括异常的产生原因、可能出现的场景处理方式针对每种异常类型列出程序应该如何处理异常情况如捕获异常并给出提示信息、重新抛出异常、进行日志记录等代码示例提供一些代码展示如何捕获和处理异常。
异常表是程序员在开发过程中非常重要的工具可以帮助他们即使识别和处理异常情况保证程序的稳定性和健壮性
异常的分类方法
异常可以按照不同的方式进行分类包括但不限于这几种
按照异常类型例如运行时异常、检查时异常、系统异常等按照异常处理方式例如捕获异常、抛出有异常、处理异常等按照异常产生时机例如同步异常、异步异常等
同步异常和异步异常是按照异常产生时机的分类方式。同步异常是指在程序执行的过程中出现了错误和异常情况导致程序停止并抛出异常需要立即处理。例如一个除法操作中的分母为零异常程序运行到该处时就会停止并抛出异常信息需要立即处理该异常情况。异步异常是指在程序运行过程中出现了错误或异常的情况但程序仍然能够继续运行不会立即抛出异常信息而是在某一个时刻由系统或程序自动处理。例如一个网络请求可能会因为网络连接的问题导致异步异常程序会尝试重新发送或者等待网络回复后再发送。
中断是另一种异常的处理方式。中断是一种异步异常它是指当计算机处理某个任务时发生了与该任务无关的事件例如硬件故障或用户输入计算机会暂停当前任务转而处理这个事件然后再返回原先的任务。中断通常由硬件设备或系统内部的程序发起可以通过中断处理程序来处理。中断的处理方式使得计算机能够同时处理多个任务提高计算机系统的效率和可靠性。
同步异常、陷阱和系统调用
同步异常、陷阱和系统调用时操作系统的三个重要的概念他们都与计算机程序的执行过程有关。
同步异常是指在程序执行的过程中出现了错误或异常情况导致程序停止运行并抛出异常需要立即处理。例如除以零、访问非法内存地址等都会引起同步异常。同步异常通常由硬件或软件自动检测并处理例如操作系统会向进程发送信号来通知发生了异常情况进程需要根据信号类型进行相应的处理。
系统调用是用户程序向操作系统请求服务的接口也是操作系统对外提供服务的接口。用户程序需要执行一些操作例如读写文件、创建进程、网络通信等。但这些操作必须由操作系统来完成。因此用户程序需要通过系统调用向操作系统发起请求让操作系统代表用户程序执行相应的服务。系统调用通常是通过软件中断来实现的即用户程序通过陷阱指令切换到内核态然后执行相应的系统调用指令操作系统在内核态完成相应的服务并将结果返回给用户程序。
总的来说同步异常、陷阱和系统调用都是计算机程序执行的过程中的异常机制和调用方式他们在操作系统中扮演着重要的角色。
故障、页面缺失、保护故障
故障、页缺失和保护故障是计算机操作系统中常见的异常类型。 故障是指当程序执行时出现的异常情况例如非法操作数、非法指令、非法内存访问等这些异常情况通常由硬件检测引发的操作系统需要处理这些异常情况。故障不同于错误错误通常是由程序逻辑或设计上的问题导致的例如除数为0、栈溢出等。
页缺失是指当程序需要访问的页面不在内存是会引发的一种异常情况操作系统会将需要访问的页面从磁盘中加载到内存中然后再让程序访问该页面。如果内存中没有改页面则会引发页缺失异常。操作系统需要将其该页面从磁盘中读取到内存中并更新页表等数据结构以便程序能够访问该页面。
保护故障是指程序试图访问受保护的资源例如内核态的资源或其他进程的内存等而没有获得足够的权限这种情况会引发异常。操作系统需要检查程序的访问权限如果没有足够的权限会引发保护故障。
总的来说故障、页缺失和保护故障都是操作系统中常见的异常类型。操作系统需要检测并处理这些异常情况以保证系统的稳定性和安全性。 下面是一个无效内存引用的例子假设我们有一个指向整型数组的指针但该指针指向了未分配的内存地址
#include stdio.h
#include stdlib.h
int main() {int *ptr;*ptr 10; // 无效内存引用printf(%d\n, *ptr);return 0;
}在该程序中我们声明了一个指向整型数组的指针ptr但没有为它分配任何内存空间。接下来我们试图将整数10赋值给指针所指向的内存地址这会导致一个无效内存引用的故障。最后程序试图读取指针所指向的内存地址中的值并将其输出但由于该内存地址未分配程序将会崩溃并退出。 为了避免这种类型的故障我们应该确保指针始终指向已分配的内存地址并在使用指针之前对其进行初始化。可以使用malloc()函数在堆上分配一段内存来避免这种类型的故障例如
#include stdio.h
#include stdlib.h
int main() {int *ptr (int *)malloc(sizeof(int));*ptr 10; // 正确的内存引用printf(%d\n, *ptr);free(ptr);return 0;
}在该程序中我们使用malloc()函数在堆上分配了一个int类型大小的内存空间并将指针ptr指向该内存地址。接下来我们将整数10赋值给该内存地址并输出该值。最后我们使用free()函数释放该内存空间。这样就避免了无效内存引用的故障
故障的例子、无效内存的引用
故障fault指的是当程序试图执行一个无效的操作或访问一个无效的内存地址时导致操作系统向该程序发送一个异常信号的情况。无效内存引用也是一种常见的故障它发生在程序试图访问一个未分配的内存地址或已释放的内存地址时。
终止
终止是指程序或进程执行结束或被强制停止的过程。
程序执行结束时会经历几个阶段。首先操作系统会将程序加载到内存中并为其分配资源或权限。然后程序开始执行直到完成它的任务或遇到异常情况。如果程序执行完成它会自动终止并退出释放掉占用的资源和权限。如果程序遇到异常情况例如发生错误或出现了无法处理的异常程序会自动终止并退出。
进程的终止可以分为正常的终止和异常的终止。正常的终止通常是指进程执行完它的任务并退出操作系统会回收进程的资源和权限。异常终止是指进程遇到了无法处理的异常情况例如访问非法内存地址、除以零等操作系统需要强制终止进程并回收资源以保证系统的稳定性和安全性。
总的来说终止是程序或进程执行结束或被强制停止的过程。它是操作系统中非常重要的部分操作系统需要确保程序和进程的正常终止并及时收回他们占用的资源和权限以保证系统的稳定性和高效性。
exit 在Unix/Linux中一个进程可以通过调用exit()系统调用来终止自己。exit()函数将在进程中止时执行一些清理操作并将进程的退出状态传递给其父进程。进程的退出状态通常用来表示进程的退出原因或者执行的结果。 在C语言中exit()函数定义在stdlib.h头文件中其原型如下
void exit(int status);其中status是一个整数表示进程的退出状态。如果status为0则表示进程正常结束。其他的退出状态可以用来表示错误代码等信息。
值得注意的是exit()函数不会直接终止进程而是会将进程的退出状态传递给其父进程由父进程决定是否终止进程。如果父进程没有等待子进程的退出状态子进程可能会成为僵尸进程需要使用wait()或waitpid()等函数来等待子进程的终止状态并回收其资源。 下面是一个简单的示例程序演示了如何使用exit()函数来终止进程
#include stdio.h
#include stdlib.h
#include unistd.hint main() {printf(before exit()\n);exit(0); // 终止进程并返回状态码0printf(after exit()\n); // 这行代码不会被执行return 0;
}在该示例程序中当调用exit()函数时进程将立即终止并返回状态码0。因此printf()函数的输出仅限于before exit()after exit()不会被执行。
Linux/x86-64常用的系统调用
Linux/x86-64是一种常见的操作系统和计算机体系结构它提供了许多系统调用供用户程序使用。下面是Linux/x86-64中常用的系统调用 read()从文件描述符中读取数据。 write()将数据写入文件描述符。 open()打开一个文件。 close()关闭一个文件。 creat()创建一个文件。 unlink()删除一个文件。 mkdir()创建一个目录。 rmdir()删除一个目录。 chdir()改变当前工作目录。 getpid()获取当前进程的进程ID。 fork()创建一个新进程。 execve()执行一个新程序。 wait()等待一个子进程退出。 pipe()创建一个管道。 dup()复制一个文件描述符。 select()等待一组文件描述符上的I/O事件。 socket()创建一个套接字。 bind()将一个套接字绑定到一个地址。 listen()监听一个套接字。 accept()接受一个客户端连接。 connect()连接到一个远程主机。 这些系统调用可以通过C语言的标准库函数或系统调用库函数来调用。它们提供了一组基本的操作系统服务用户程序可以通过它们来完成文件操作、进程管理、网络通信等功能。
syscall、open
syscall是Linux操作系统提供的系统调用机制它允许用户程序直接调用操作系统内核中的功能。在Linux/x86-64中syscall指令用于触发系统调用该指令会将系统调用号和参数传递给操作系统内核内核会根据系统调用号和参数执行相应的操作并返回结果给用户程序。 open是一个常用的系统调用用于打开一个文件。在Linux/x86-64中open系统调用的系统调用号为2它有三个参数 (1)const char *pathname要打开的文件的路径名。 (2)int flags打开文件的方式和标志位例如O_RDONLY、O_WRONLY、O_RDWR等。 (3)mode_t mode文件的权限例如0666表示文件可读可写。
用户程序可以通过C语言的标准库函数open()来调用open系统调用例如
#include fcntl.h
int open(const char *pathname, int flags, mode_t mode);该函数返回一个文件描述符用于后续的文件读写操作。例如要打开一个名为test.txt的文件并写入数据可以按如下方式编写程序
#include stdio.h
#include fcntl.h
#include unistd.h
int main() {int fd open(test.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd -1) {perror(open);return 1;}write(fd, Hello, World!, 13);close(fd);return 0;
}该程序使用open()函数打开了test.txt文件并以写入模式打开。如果打开文件失败程序会打印出错误信息并退出。如果打开成功程序会通过write()函数向文件中写入Hello, World!字符串并关闭文件描述符。
进程
进程是计算机中正在运行的一个程序实例。在操作系统中进程是资源分配和调度的基本单位也可以看作是程序在计算机中的依次执行活动。一个进程可以包括多个线程每个线程执行程序中的一部分任务。
进程拥有自己的内存空间、寄存器集合、程序计数器、打开的文件等系统资源他们之间相互独立互不干扰。操作系统通过进程调度算法来管理进程的运行分配CPU时间片以实现多任务并发执行。
每个进程都有一个唯一的进程标识符Process IdentifierPID用于区分不同的进程。当一个程序被启动时操作系统会为它创建一个新的进程并分配一个唯一的PID。
进程的状态可以分为三种运行态Running、就绪态Ready和阻塞态Blocked。当进程正在执行时它处于运行态当进程等待某些事件的发生时如等待输入输出、等待资源分配等则处于阻塞态当进程已经准备好运行但还没有获得CPU时间片时则处于就绪态。
进程间可以通过进程间通信机制如管道、共享内存、消息队列等来进行通信和数据共享。同时操作系统也提供了一些进程管理的系统调用如创建进程、销毁进程、等待进程结束等。 状态详解:
运行态----当进程被分配到cpu并开始执行时,它就处于运行态.此时,进程正在执行指令,使用cpu资源,并且可以与其他进程竞争cpu时间片.在运行态中,进程可以执行任何可以在用户或内核模式下执行的操作,例如读写文件,发送方网络请求.阻塞态-----当进程等待某些事件的发生而无法继续执行时它就处于阻塞态。例如当进程等待IO操作完成、等待锁或信号量、等待另一个进程发送消息等时它就处于阻塞态。在阻塞状态中进程不会使用CPU资源并且不会竞争CPU时间片。终止态------当进程完成其执行任务并退出时,它就处于终止态.在这个状态下,进程会释放其使用的所有资源,包括cpu时间,内存,文件描述,锁等等.终止的进程的信息会留在进程表中,以供父进程或其他进程查看进程的退出状态信息. 这三种状态是操作系统中进程的基本状态它们的转换和管理是操作系统中进程调度和资源管理的关键部分。
进程的两个抽象 独占cpu和寄存器 进程的两个抽象是指进程在操作系统中的两个最基本的抽象概念分别是进程的执行状态和进程的地址空间。 第一个抽象是指进程在运行时独占CPU和寄存器可以看作是一个独立的执行单位。进程在执行是会占用cpu的时间片通过cpu的切换来实现多任务并发执行。在进程运行期间CPU和寄存器都被进程独占进程可以使用这些资源来执行自己的代码读取和写入自己的数据。 拥有自己的地址空间 第二个抽象是指每个进程都拥有自己的地址空间也称为虚拟地址空间包括代码、数据和堆栈等。每个进程在运行时都有自己的代码、数据、堆栈等内容而且每个进程的地址空间是相互独立的互不干扰。操作系统通过虚拟内存管理机制来实现地址空间的分配和保护使得每个进程都能独立运行保证了系统的安全性和稳定性。
进程的地址空间是虚拟的即进程认为自己独占了整个物理内存但实际上它只能访问被操作系统分配给它的部分内存。这是由于现代操作系统采用了虚拟内存技术将物理内存划分成许多大小相等的页面每个页面都有一个虚拟地址和一个物理地址操作系统通过映射表来管理虚拟地址和物理地址之间的对应关系。每个进程都有自己的映射表使得进程在访问内存时只能访问自己的地址空间。
进程的地址空间通常包括以下几个部分 1.代码段存放程序的可执行代码。 2.数据段存放程序中定义的全局变量和静态变量 3.堆存放动态分配的内存如使用malloc函数分配的内存 4.栈存放函数调用构成中的局部变量和函数参数
top指令看进程 在 Linux 系统中top 命令可以用于实时监测系统中的进程状态和资源占用情况提供了一个交互式的终端界面显示了各个进程的 CPU 占用率、内存使用情况、运行时间等信息。
要使用 top 命令可以在终端中输入 top然后按下回车键。在 top 命令的界面中可以看到系统中所有运行的进程的列表每个进程占据一行。每一行显示了进程的 PID进程标识符、进程所属的用户、进程的 CPU 占用率、内存使用情况等信息。
在 top 命令的界面中可以使用一些快捷键来执行一些操作比如 P按 CPU 占用率排序 M按内存使用情况排序 k杀死一个进程 H显示线程信息。
此外还可以使用 top -p 命令来查看指定 PID 的进程信息。例如要查看 PID 为 123 的进程信息可以在终端中输入 top -p 123。
多进程管理共享上下文切换是地址空间和寄存器的变化 在一个操作系统中多个进程可能同时运行。这些进程之间可能需要共享一些资源例如内存和文件等。为了保证进程间的安全和独立性操作系统采用了多进程管理机制。
在多进程管理机制下,每个进程都有自己的独立地址空间和寄存器.地址空间是指进程可以使用的内存空间,包括代码\数据\栈堆等.寄存器则是cpu内部的一些寄存器,用于存储cpu执行指令时所需要的数据.
当操作系统需要切换进程时,会保存当前进程的地址空间和寄存器的状态.然后加载新进程的地址空间和寄存器的状态.这个这个过程称为上下文切换.在上下文切换中,操作系统需要做两件事情: (1)保存当前进程的状态.操作系统需要保存当前的地址空间和寄存器状态,以便之后能够恢复到当前进程的执行状态. (2)加载新进程的状态.操作系统需要加载新进程的地址空间和寄存器状态,以便让CPU开始执行新进程的代码.
在多进程管理机制下,不同进程之间的地址空间和寄存器状态是相互独立的.这样可以保证进程之间的数据不会相互干扰,提高了系统的安全性和稳定性.但是,由于上下文切换需要保存和恢复大量的状态信息,因此会带来一定的性能开销.
上下文切换的过程 上下文切换是指在操作系统中由于多个进程或线程共享cpu资源当一个进程或线程需要让出cpu资源时操作系统需要保存当前进程或线程的上下文信息切换到下一个进程或线程的上下文信息然后重新启动执行新的进程或线程
上下文切换的过程通常包括以下步骤 当前进程或线程执行到了一个需要等待外部事件发生的操作如 I/O 操作或者执行了一个时间片结束的操作需要让出 CPU 资源操作系统就会触发上下文切换。 操作系统会保存当前进程或线程的上下文信息包括 CPU 寄存器的值、程序计数器、堆栈指针等等这些信息被保存在操作系统内核的进程或线程控制块PCB/TCB中。 操作系统会根据进程或线程调度算法选择下一个需要执行的进程或线程将其上下文信息加载到 CPU 中。 操作系统会将 CPU 控制权交给下一个进程或线程CPU 开始执行新的进程或线程。 如果之前被暂停的进程或线程仍然是就绪状态它将被重新加入到可执行队列中等待下一次调度。 上下文切换是一种非常耗费资源的操作因为在保存和恢复进程或线程上下文的过程中需要涉及到内存读写和 CPU 寄存器的操作这些操作都需要消耗时间和 CPU 资源。因此对于需要频繁切换的应用程序上下文切换可能成为瓶颈影响系统的性能。
** 进程的控制函数,getpid,getppid:**在Unix/Linux中可以使用 getpid 函数来获取当前进程的进程ID使用 getppid 函数来获取当前进程的父进程的进程ID。这两个函数是进程控制函数的基本组成部分之一它们可以帮助我们识别和控制进程。
逻辑控制流
逻辑控制流是指程序在执行时所遵循的顺序和流程。在一个程序中可能存在多个语句和操作这些语句和操作之间的关系和顺序构成了程序的逻辑控制流。程序的逻辑控制流通常由以下几种结构组成 (1)顺序结构程序按照语句的顺序依次执行没有任何分支或循环。这是最简单的程序结构也是所有程序结构的基础。 (2)分支结构程序根据条件执行不同的语句。分支结构通常由 if 语句和 switch 语句实现。 (3)循环结构程序根据循环条件反复执行一段代码。循环结构通常由 while 语句、for 语句和 do-while 语句实现。 (4)跳转结构程序可以在任意位置跳转到另一个位置继续执行。跳转结构通常由 goto 语句实现但是在实际编程中不建议使用。 这些程序结构可以组合使用形成更加复杂的程序流程。例如可以在分支结构中嵌套循环结构或者在循环结构中使用分支结构。在实际编程中选择合适的程序结构组合可以提高程序的效率和可读性。
并发
并发是指在同一时间内,多个任务或者进程都在执行在操作系统中常常需要同时处理多个任务或进程这就需要使用并发记住来实现
并发技术包括多进程多线程异步编程等步骤多进程指的是在操作系统中同时运行多个独立的进程每个进程都有自己的地址空间和寄存器状态多线程则是在一个进程中同时运行多个独立的进程每个线程都有自己的堆栈和寄存器状态但是共享进程的地址空间异步编程则是指使用异步回调和事件循环等技术是现在单线程中处理多个任务的同时保证响应速度和资源利用率
并发技术的优点可以提高系统的响应速度和资源利用率是的多个任务可以同时运行提高系统的效率和可靠性但是并发技术也带来了一些问题例如线程安全问题死锁问题竞态条件等为了避免这些问题需要使用一些并发编程的最佳实践和设计模式如使用锁机制避免共享数据等
用户模式和内核模式
用户模式和内核模式是操作系统中两种不同的执行模型
当程序运行在用户模式时它只能访问受限资源例如它自己在进程地址空间而不能直接访问系统资源如硬件设备和内核代码当程序需要访问系统资源时它必须通过系统调用进入内核模式由内核完成相关操作用户模式是一种安全机制可以预防应用程序错误或恶意代码对系统造成损害
内核模式也被称为特权模式或系统模式因为在这种模式下操作系统拥有对系统资源的完全控制权内核模式下的代码可以直接访问所有硬件设备和内存空间并执行特权指令如设置中断向量表和操作状态由于内核模式下的代码可以访问系统资源因此必须确保内核代码的正确性和安全性
当一个进程发起系统调用时他会从用户模式切换到内核模式系统会在内核模式下执行相应的操作并返回结果给用户模式的进程这个过程称为上下文切换也是操作系统中常见的一种机制因为上下文切换涉及到状态的切换和内存访问的切换因此它的开销比较大需要尽可能减少上下文切换的次数以提高系统的性能
中断在状态转换中的作用: 当应用程序在用户空间运行时它只能访问自己的内存空间和CPU提供的有限资源。如果应用程序需要访问内核空间的资源例如设备驱动程序或系统服务它必须通过系统调用进入内核空间。这是因为内核空间包含了所有系统资源和硬件设备它具有更高的权限和更多的特权。
在现代操作系统中用户空间和内核空间是完全隔离的不能直接访问对方的内存空间。因此当应用程序需要访问内核空间时它必须使用中断操作。中断是一种在CPU执行指令时暂停当前任务的机制以响应硬件事件或软件请求。当应用程序调用系统调用时它会触发一个中断将CPU的控制权转移到内核空间。内核会执行所需的操作然后返回结果给应用程序并将控制权交还给应用程序。
因此通过中断操作应用程序可以安全地访问内核空间而不会破坏系统的安全和稳定性。同时内核可以对应用程序的请求进行安全验证和控制以确保系统的安全和稳定性。
系统调用错误处理
在操作系统中当应用程序发起系统调用时可能发生错误错误可能是由于应用程序本身的错误或操作系统的问题引起的操作系统通常会返回一个错误的代码以通知应用程序系统调用发生了什么问题应用程序需要正确处理这些错误以便恰当地相应错误情况
系统调用的错误处理通常包括以下步骤 (1)检查系统调用的返回值。系统调用通常会返回一个整数值表示操作的结果或错误代码。应用程序需要检查这个返回值以确定操作是否成功。 (2)如果系统调用返回一个错误代码应用程序需要根据错误类型采取适当的措施。例如如果打开文件失败应用程序可以选择重新尝试打开文件或者向用户显示错误消息。 (3)应用程序需要在代码中添加适当的错误处理代码。例如如果打开文件失败应用程序需要添加代码来关闭打开的文件描述符以便在错误情况下释放资源。 (4)应用程序可以选择将错误消息记录到日志文件中以便在后续排查错误时使用。 在处理系统调用错误时应用程序需要仔细考虑错误类型和处理方式以确保应用程序的正确性和稳定性。
错误报告功能(unix_error)
unix_error 是一个错误报告函数它通常用于处理 UNIX 系统调用中的错误。它位于 CSAPP 案例中的 csapp.c 文件中其主要功能是将 UNIX 系统调用的错误转换为人类可读的错误消息并将错误信息打印到标准错误流stderr中。它的实现如下
void unix_error(char *msg) /* UNIX-style error */
{fprintf(stderr, %s: %s\n, msg, strerror(errno));exit(0);
}该函数接收一个字符串参数 msg表示错误消息的前缀。它使用 strerror 函数将错误码 errno 转换为错误消息并将前缀和错误消息一起打印到标准错误流中。最后它调用 exit 函数退出程序。
例如如果调用 open 函数打开文件时发生错误可以使用 unix_error 函数报告错误消息
int fd open(nonexistent_file, O_RDONLY);
if (fd -1) {unix_error(open error);
}在上述代码中如果打开文件失败unix_error 函数将打印以下错误消息
open error: No such file or directory使用 unix_error 函数可以方便地处理 UNIX 系统调用中的错误并提供更好的错误报告功能。
错误处理包装函数(Fork)
在操作系统中错误处理是一个很重要的问题。为了方便错误处理可以使用错误处理包装函数来包装系统调用并检查它们是否成功。这些包装函数将系统调用的错误转换为返回值以方便应用程序检查错误并采取适当的措施。 以下是一个错误处理包装函数 Fork 的示例它封装了 fork 系统调用
pid_t Fork(void)
{pid_t pid;if ((pid fork()) 0)unix_error(Fork error);return pid;
}该函数返回子进程的进程 ID。如果调用 fork 函数失败该函数将打印错误消息并终止程序。 在使用 Fork 函数时如果发生错误它将抛出异常并终止程序。例如
pid_t pid Fork();
if (pid 0) { // Child process// ...
} else if (pid 0) { // Parent process// ...
} else { // Errorprintf(Fork error\n);exit(1);
}在上述代码中如果调用 Fork 函数失败程序将打印错误消息并终止。否则它将分别在父进程和子进程中执行不同的操作。
fork创建子进程,返回两次 fork的例子 在Unix/Linux中fork()是一个用于创建子进程的系统调用。调用fork()函数后将会创建一个新的进程这个进程就是原进程的子进程。这个子进程将会与父进程拥有相同的代码和数据但是拥有不同的进程ID和系统资源例如文件描述符、内存映射、定时器等。fork()函数的原型如下
#include unistd.h
pid_t fork(void);其中pid_t是一个整型数据类型表示进程的ID。fork()函数将返回两次在父进程中返回新的子进程的PID在子进程中返回0。如果fork()函数调用失败则返回一个负数。 下面是一个简单的示例程序演示了如何使用fork()函数来创建子进程
#include stdio.h
#include unistd.hint main() {pid_t pid fork(); // 创建子进程if (pid 0) { // 创建进程失败printf(fork error\n);} else if (pid 0) { // 子进程printf(child process: pid%d\n, getpid());} else { // 父进程printf(parent process: child pid%d\n, pid);}return 0;
}在该示例程序中首先调用fork()函数来创建子进程。如果fork()函数成功创建了一个子进程则在子进程中输出child process: pidXXX其中XXX是子进程的进程ID在父进程中输出parent process: child pidXXX其中XXX是新创建子进程的进程ID。注意父进程和子进程会执行相同的代码但是它们的执行顺序和输出可能会有所不同。
需要注意的是fork()函数会复制父进程的内存映像包括所有打开的文件描述符、信号处理函数、虚拟内存等。因此父进程和子进程之间不会共享任何变量或数据结构。如果需要在父进程和子进程之间共享数据可以使用进程间通信IPC机制例如管道、共享内存、消息队列等。
捕捉调用fork时发生的情况 用于捕捉调用fork时发生的情况其中顶点表示语句的执行边对应变量: 这个进程图描述了一个程序该程序在开始时输出一条消息然后调用fork创建一个子进程。在子进程中程序输出一条消息并通过调用exit()来结束子进程。在父进程中程序输出一条消息然后继续执行并在最后输出一条消息。
在这个进程图中顶点表示程序中的不同语句边表示控制流。特别地边上的标签表示与该边相关的变量或条件。在这个示例中pid是一个变量用于存储fork返回的值。如果pid等于0则表示当前执行的是子进程否则表示当前执行的是父进程。
进程图例子
下面是一个示例进程图用于说明一个简单的进程该进程会打印一条消息并等待用户的输入。当用户输入一个字符串后进程会打印出该字符串并结束。 在这个进程图中顶点表示程序中的不同语句边表示控制流。在这个示例中程序首先打印一条消息然后等待用户的输入。输入完成后程序会将输入的字符串存储在名为buffer的变量中并打印出该字符串。最后程序通过调用exit()来结束进程。
进程图中的箭头表示进程的控制流。这个示例虽然简单但是它展示了进程图可以用于描述程序的控制流。通过将程序转换为进程图我们可以更好地理解程序的执行过程并更好地分析它的行为。
思考?
每个进程都有子进程和父进程吗? 是的,每个进程都会有一个父进程和可能会有一个或多个子进程,当一个进程调用fork()函数时,他就会创建一个新的子线程,并且这个子线程将会是当前进程的副本.这个子进程将会与父进程拥有相同的代码和数据但是它们会拥有各自独立的地址空间。
子进程会从父进程继承一些属性例如文件描述符、用户ID和组ID等等。子进程与父进程在创建时的状态会有些不同其中最重要的是子进程会继承父进程的执行上下文。也就是说当子进程开始运行时它会从父进程的当前指令处开始执行然后它会继续自己的执行路径。
一旦子进程创建成功后父进程和子进程就会开始并行运行。它们拥有各自独立的执行上下文所以它们的行为是相互独立的。当子进程完成后它会向父进程发送一个信号告诉父进程它已经结束了。父进程可以使用wait()或waitpid()等函数来等待子进程结束并检查它的状态或者直接忽略子进程的退出信号。
总之每个进程都有一个父进程和可能有一个或多个子进程。这些进程在创建时的状态可能有些不同但它们是相互独立的可以在系统中并行运行。
wait工作原理的简单示例
假设我们有一个父进程和两个子进程子进程1会休眠2秒钟子进程2会休眠3秒钟然后结束。父进程要等待两个子进程都结束后才退出。这个过程可以使用wait()函数来实现示例代码如下
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/wait.hint main()
{pid_t pid1, pid2;int status1, status2;pid1 fork();if (pid1 0) {// 子进程1sleep(2);printf(Child process 1 is exiting.\n);exit(0);} else {pid2 fork();if (pid2 0) {// 子进程2sleep(3);printf(Child process 2 is exiting.\n);exit(0);} else {// 父进程wait(status1);printf(Child process 1 has exited with status %d.\n, status1);wait(status2);printf(Child process 2 has exited with status %d.\n, status2);printf(Parent process is exiting.\n);exit(0);}}return 0;
}在这个例子中父进程首先fork()出两个子进程然后通过wait()函数来等待子进程结束并回收它们的资源。当子进程结束时它会向父进程发送一个信号父进程在wait()函数中捕捉到这个信号并从子进程那里获取到子进程的退出状态。
运行这个程序后我们可以看到如下输出
wait()函数 wait()函数是用来等待子进程结束并回收它们的资源的.当一个子进程结束后,它并不会立即从系统中消失,而是会成为一个僵尸进程,这个进程的资源(包括内存,文件描述,状态等)仍然会被占用,但是它已经不能执行任何操作了.
父进程可以使用wait()函数来等待子进程结束,并回收子进程的资源,这样子进程就可以被完全从系统中清除掉.在父进程调用wait()函数之前,子进程的资源不会被回收,而会一直占用着系统的资源.
当父进程调用wait()函数时,它会阻塞并等待子进程结束.如果有多个子进程同时结束,wait()函数会返回其中一个子进程的状态,并将这个子进程的资源回收.如果父进程没有等待到任何子进程结束,wait()函数会一直阻塞,直到有子进程结束为止.
总之wait()函数用来等待子进程结束并回收它们的资源。如果父进程没有回收子进程的资源子进程就会变成僵尸进程占用着系统的资源。
waitpid函数: waitpid函数可以等待指定的子进程结束,并回收它的资源.它有三个参数: pid要等待的子进程的进程ID如果为-1则等待任何一个子进程结束。 status保存子进程退出状态的指针。 options选项参数。
#include stdio.h
#include stdlib.h
#include sys/types.h
#include sys/wait.h
#include unistd.hint main()
{pid_t pid1, pid2;int status1, status2;pid1 fork();if (pid1 0) {perror(fork);exit(EXIT_FAILURE);} else if (pid1 0) {// 子进程1printf(I am child process 1. My pid is %d.\n, getpid());sleep(2);exit(1);} else {pid2 fork();if (pid2 0) {perror(fork);exit(EXIT_FAILURE);} else if (pid2 0) {// 子进程2printf(I am child process 2. My pid is %d.\n, getpid());sleep(4);exit(2);} else {// 父进程printf(I am parent process. My pid is %d.\n, getpid());printf(Waiting for child process %d and %d...\n, pid1, pid2);// 等待子进程1结束waitpid(pid1, status1, 0);if (WIFEXITED(status1)) {printf(Child process %d terminated with exit status %d.\n, pid1, WEXITSTATUS(status1));} else {printf(Child process %d terminated abnormally.\n, pid1);}// 等待子进程2结束waitpid(pid2, status2, 0);if (WIFEXITED(status2)) {printf(Child process %d terminated with exit status %d.\n, pid2, WEXITSTATUS(status2));} else {printf(Child process %d terminated abnormally.\n, pid2);}}}return 0;
}该程序创建了两个子进程分别等待2秒和4秒后退出。父进程使用waitpid函数等待子进程结束并打印子进程的退出状态。输出如下 可以看到父进程先等待子进程1结束然后等待子进程2结束。子进程的退出状态分别是1和2。
execve运行不同的程序 execve()是一个Linux/Unix系统调用, 可以被用来启动一个全新的程序并运行, 从而使当前进程的代码和数据被替换为新程序的代码和数据.这样可以用来实现不同程序的功能. execve接收三个参数 (1)path指定要执行的程序的路径和名称。 (2)argv是一个字符串数组其中包含了程序的命令行参数。argv的第一个元素通常是程序的名称后面的元素是程序的参数。 (3)envp是一个字符串数组其中包含了程序的环境变量。
execve会首先通过path指定的路径查找要执行的程序文件.如果找到了对应的文件,则用该文件替换当前进程,并开始执行新的程序.如果找不到对应的文件,则execve函数调用失败并返回-1. 示例:
#include stdio.h
#include stdlib.h
#include unistd.hint main() {char *argv[] {/bin/ls, -l, /tmp, NULL}; // 要执行的程序和参数char *envp[] {NULL}; // 环境变量if (execve(/bin/ls, argv, envp) -1) {printf(execve failed\n);exit(1);}return 0;
}需要注意的是execve函数调用成功后当前进程就会被替换为新的程序因此在execve后面的代码不会被执行。如果要在新程序执行完成后继续执行原程序中的代码可以在新程序中调用exit函数来结束程序并在原程序中使用fork和waitpid等系统调用来等待新程序的执行结果。
新程序开始栈帧结构
在一个新程序被启动时,它的栈帧结构包含了新程序的入口点\命令行参数以及环境变量等信息.具体来说,栈帧结构的构成通常包括一下几个部分: argc一个整数表示程序接收到的命令行参数的数量。 argv一个指针数组指向程序接收到的命令行参数的字符串。
int main(int argc, char *argv[]);envp一个指针数组指向程序使用的环境变量。 返回地址一个指向程序入口点的地址。 局部变量程序在执行过程中可能会创建一些局部变量这些变量会被存储在栈帧结构中。 这些信息会在新程序的栈帧结构中被依次存储。在程序执行的过程中这些信息可以被访问和修改。一般来说操作系统会负责创建和初始化新程序的栈帧结构从而为新程序提供一个合适的执行环境。
僵尸进程
僵尸进程Zombie Process是指一个已经完成执行已经退出的进程但是其父进程尚未调用wait()或waitpid()等系统调用来获取其终止状态从而导致该进程的进程描述符仍然存在于系统中但是已经无法执行任何操作也无法被调度。
在Linux系统中僵尸进程的进程状态标记为Z或Z可以通过执行ps命令来查看。僵尸进程不会占用太多系统资源但是如果父进程一直不调用wait()等系统调用来回收该进程的资源那么它们会逐渐积累最终导致系统资源的浪费。
为了避免僵尸进程的产生父进程在创建子进程后应该及时调用wait()或waitpid()等系统调用来获取子进程的退出状态。如果父进程无法及时调用这些系统调用可以考虑使用信号处理程序或者使用守护进程等方式来解决。
系统安排init进程回收孤儿进程
当一个进程的父进程在它退出前先退出了该进程就会成为孤儿进程Orphan Process也就是没有父进程的进程。在Linux系统中这些孤儿进程会被系统自动分配给进程号为1的init进程作为它们的新的父进程这样就避免了孤儿进程的存在。
init进程是Linux系统的第一个进程它负责启动其他进程并监控它们的运行状态。当一个进程成为孤儿进程时它会被系统重新分配给init进程作为它的父进程。这样当init进程调用wait()或waitpid()等系统调用时就能够获取并回收这些孤儿进程的资源避免了系统资源的浪费。
需要注意的是init进程只负责回收孤儿进程对于僵尸进程则需要其父进程调用wait()等系统调用来进行回收否则僵尸进程会一直存在导致系统资源的浪费。 例子:
#include stdio.h
#include stdlib.h
#include unistd.hint main() {pid_t pid;pid fork();if (pid 0) {printf(Fork Failed\n);exit(1);}else if (pid 0) {printf(Child process\n);while (1) {sleep(1); // 子进程不退出一直在运行}}else {printf(Parent process\n);exit(0); // 父进程退出}return 0;
}在该程序中父进程创建了子进程并在创建后立即退出了但是子进程仍然在运行。执行该程序时可以通过执行ps命令查看进程状态如下所示
一个僵尸现象的例子: 假设有一个父进程和一个子进程,父进程创建了子进程,并等待子进程的结束时,它的进程描述符仍然存在于系统中但是父进程尚未调用wait()或waitpid()等系统调用来获取它的终止状态。此时子进程就会成为一个僵尸进程。.
#include stdio.h
#include stdlib.h
#include sys/wait.h
#include unistd.hint main() {pid_t pid;pid fork();if (pid 0) {printf(Fork Failed\n);exit(1);}else if (pid 0) {printf(Child process\n);exit(0);}else {printf(Parent process\n);sleep(2); // 等待子进程结束// 父进程未调用wait()或waitpid()等系统调用获取子进程终止状态printf(Parent process exit without calling wait()\n);}return 0;
}在该程序中父进程创建了子进程并等待子进程结束。但是在父进程中并没有调用wait()或waitpid()等系统调用来获取子进程的终止状态从而导致子进程成为了一个僵尸进程。执行该程序时可以通过执行ps命令查看进程状态如下所示 可以看到进程状态标记为Z或Z也就是僵尸进程。这种情况下需要父进程调用wait()或waitpid()等系统调用来获取子进程的终止状态从而回收它的资源。 文章转载自: http://www.morning.pzrnf.cn.gov.cn.pzrnf.cn http://www.morning.ljdtn.cn.gov.cn.ljdtn.cn http://www.morning.wrysm.cn.gov.cn.wrysm.cn http://www.morning.pgzgy.cn.gov.cn.pgzgy.cn http://www.morning.haibuli.com.gov.cn.haibuli.com http://www.morning.gjxr.cn.gov.cn.gjxr.cn http://www.morning.lfpdc.cn.gov.cn.lfpdc.cn http://www.morning.hnk25076he.cn.gov.cn.hnk25076he.cn http://www.morning.nrmyj.cn.gov.cn.nrmyj.cn http://www.morning.ctxt.cn.gov.cn.ctxt.cn http://www.morning.xrlwr.cn.gov.cn.xrlwr.cn http://www.morning.tgtwy.cn.gov.cn.tgtwy.cn http://www.morning.lizpw.com.gov.cn.lizpw.com http://www.morning.dwrjj.cn.gov.cn.dwrjj.cn http://www.morning.rhkmn.cn.gov.cn.rhkmn.cn http://www.morning.cgtfl.cn.gov.cn.cgtfl.cn http://www.morning.mqmmc.cn.gov.cn.mqmmc.cn http://www.morning.ttxnj.cn.gov.cn.ttxnj.cn http://www.morning.beeice.com.gov.cn.beeice.com http://www.morning.gthgf.cn.gov.cn.gthgf.cn http://www.morning.tgtsg.cn.gov.cn.tgtsg.cn http://www.morning.zfkxj.cn.gov.cn.zfkxj.cn http://www.morning.pqktp.cn.gov.cn.pqktp.cn http://www.morning.wfpmt.cn.gov.cn.wfpmt.cn http://www.morning.xcfmh.cn.gov.cn.xcfmh.cn http://www.morning.zwckz.cn.gov.cn.zwckz.cn http://www.morning.rsfp.cn.gov.cn.rsfp.cn http://www.morning.wffxr.cn.gov.cn.wffxr.cn http://www.morning.rjnrf.cn.gov.cn.rjnrf.cn http://www.morning.gstg.cn.gov.cn.gstg.cn http://www.morning.wqrdx.cn.gov.cn.wqrdx.cn http://www.morning.ktmbp.cn.gov.cn.ktmbp.cn http://www.morning.xhxsr.cn.gov.cn.xhxsr.cn http://www.morning.nxbsq.cn.gov.cn.nxbsq.cn http://www.morning.dmcqy.cn.gov.cn.dmcqy.cn http://www.morning.jxltk.cn.gov.cn.jxltk.cn http://www.morning.rlxnc.cn.gov.cn.rlxnc.cn http://www.morning.bqmdl.cn.gov.cn.bqmdl.cn http://www.morning.jglqn.cn.gov.cn.jglqn.cn http://www.morning.sfhjx.cn.gov.cn.sfhjx.cn http://www.morning.xdpjf.cn.gov.cn.xdpjf.cn http://www.morning.hzryl.cn.gov.cn.hzryl.cn http://www.morning.rccbt.cn.gov.cn.rccbt.cn http://www.morning.qytby.cn.gov.cn.qytby.cn http://www.morning.lbzgt.cn.gov.cn.lbzgt.cn http://www.morning.llcgz.cn.gov.cn.llcgz.cn http://www.morning.sbkb.cn.gov.cn.sbkb.cn http://www.morning.rlbg.cn.gov.cn.rlbg.cn http://www.morning.ljtwp.cn.gov.cn.ljtwp.cn http://www.morning.junyaod.com.gov.cn.junyaod.com http://www.morning.sbqrm.cn.gov.cn.sbqrm.cn http://www.morning.srkqs.cn.gov.cn.srkqs.cn http://www.morning.fmgwx.cn.gov.cn.fmgwx.cn http://www.morning.nlpbh.cn.gov.cn.nlpbh.cn http://www.morning.mfnjk.cn.gov.cn.mfnjk.cn http://www.morning.mywnk.cn.gov.cn.mywnk.cn http://www.morning.mjmtm.cn.gov.cn.mjmtm.cn http://www.morning.tpnch.cn.gov.cn.tpnch.cn http://www.morning.gqwpl.cn.gov.cn.gqwpl.cn http://www.morning.pqjlp.cn.gov.cn.pqjlp.cn http://www.morning.qszyd.cn.gov.cn.qszyd.cn http://www.morning.fwcnx.cn.gov.cn.fwcnx.cn http://www.morning.hjjkz.cn.gov.cn.hjjkz.cn http://www.morning.rtpw.cn.gov.cn.rtpw.cn http://www.morning.stflb.cn.gov.cn.stflb.cn http://www.morning.pwlxy.cn.gov.cn.pwlxy.cn http://www.morning.fkffr.cn.gov.cn.fkffr.cn http://www.morning.bqrd.cn.gov.cn.bqrd.cn http://www.morning.xyjlh.cn.gov.cn.xyjlh.cn http://www.morning.wqfrd.cn.gov.cn.wqfrd.cn http://www.morning.c7623.cn.gov.cn.c7623.cn http://www.morning.cklld.cn.gov.cn.cklld.cn http://www.morning.tbnpn.cn.gov.cn.tbnpn.cn http://www.morning.ksgjy.cn.gov.cn.ksgjy.cn http://www.morning.mngh.cn.gov.cn.mngh.cn http://www.morning.ktnt.cn.gov.cn.ktnt.cn http://www.morning.hjwkq.cn.gov.cn.hjwkq.cn http://www.morning.xblrq.cn.gov.cn.xblrq.cn http://www.morning.hwljx.cn.gov.cn.hwljx.cn http://www.morning.sxwfx.cn.gov.cn.sxwfx.cn