赞
踩
目录
上文讲述了进程的概念,现在大家对于进程的定义已经有所了解了,本文主要介绍一下进程的基本信息,例如进程是怎么创建和结束的,怎么用操作系统提供的API去自己创建一个进程?
创建:在平时我们打开一个软件只需要双击图标,软件就会自己运行,或者在shell中输入命令就可以创建一个进程来运行程序,那么程序是如何转化为进程的呢?
上图直观的显示出了这个过程,程序运行时首先操作系统会将程序运行需要的数据存入到内存,存入到进程的地址空间中。此时已经将程序的运行代码存到了进程的地址空间中,还需要分配一些堆栈空间用于程序执行,此时就完成了准备工作,最后就是启动程序运行,OS将CPU的控制权转移给进程,从而程序开始执行。
操作系统为每个进程分配独立的地址空间,确保进程之间的内存空间相互隔离,不会相互干扰或访问彼此的数据。,所以我们可以运行多个程序并且他们互不干扰
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
-
- int main() {
- pid_t pid = fork();
-
- if (pid < 0) {
- // fork() 出错
- fprintf(stderr, "Fork failed\n");
- return 1;
- }
- else if (pid == 0) {
- // 子进程
- printf("This is the child process\n");
- }
- else {
- // 父进程
- printf("This is the parent process\n");
- }
-
- return 0;
- }
子进程并不是完全拷贝了父进程。具体来说,虽然它拥有自己的地址空间(即拥有自己的私有内存)、寄存器、程序计数器等,但是它从fork()返回的值是不同的。fork()
调用返回两次,一次在父进程中返回子进程的进程 ID(PID),一次在子进程中返回0。通过这种方式,父进程和子进程可以根据返回值来确定自己的身份。
上述代码如果你多次运行后会发现运行后产生的结果不是固定的,有的情况下父进程会早于子进程被打印出来,这种不确定性是因为CPU调度程序来决定哪个进程先被执行,感兴趣可以了解一下系统调度算法
wait()是一个系统调用(或函数),用于父进程等待其子进程的结束。它允许父进程暂停执行,直到子进程完成执行为止。
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <unistd.h>
-
- int main() {
- pid_t pid = fork();
-
- if (pid < 0) {
- // fork() 出错
- fprintf(stderr, "Fork failed\n");
- return 1;
- }
- else if (pid == 0) {
- // 子进程
- printf("This is the child process\n");
- sleep(2); // 模拟子进程执行一段时间
- return 42;
- }
- else {
- // 父进程
- printf("This is the parent process\n");
- int status;
- pid_t child_pid = wait(&status);
-
- if (WIFEXITED(status)) {
- int exit_status = WEXITSTATUS(status);
- printf("Child process %d exited with status %d\n", child_pid, exit_status);
- }
- else {
- printf("Child process %d did not exit normally\n", child_pid);
- }
- }
-
- return 0;
- }
wait() 函数会阻塞父进程的执行,直到一个子进程结束。在子进程结束后,父进程会继续执行,并从 wait() 调用中返回。
wait() 函数的参数 status 是一个指向整型变量的指针,用于存储子进程的退出状态信息。可以使用宏函数 WIFEXITED(status)、WEXITSTATUS(status) 等来检查和获取子进程的退出状态。
wait() 函数可以等待任意一个子进程结束,也可以使用 waitpid() 函数等待指定的子进程。
给我可执行程序的名称及需要的参数后,exec()会从可执行程序中加载代码和静态数据,并用它覆写自己的代码段(以及静态数据)堆、栈及其他内存空间也会被重新初始化。然后操作系统就执行该程序,将参数通过argv传递给该进程。因此,它并没有创建新进程,而是直接将当前运行的程序替换为不同的运行程序
exec 系列的函数有多个变体,包括 execl、execv、execle、execve 等,每个变体在使用上略有不同,但它们的基本目标都是相同的:加载并执行一个新的程序。
我们主要介绍其中的两个:
成功无返回,失败返回-1。
- #include <unistd.h>
-
- int execlp(const char *file, const char *arg0, ..., (char *) NULL);
execlp 函数接受可执行文件的名称(不需要完整路径)和一系列参数,以及一个以 NULL 结束的参数列表。它会在系统的路径中搜索与提供的文件名称匹配的可执行文件,找到后将加载并执行该程序。
成功无返回,失败返回-1。
- #include <unistd.h>
-
- int execl(const char *path, const char *arg0, ..., (char *) NULL);
加载一个进程,通过路径和程序名来加载
除了上面提到的 fork()、wait()和 exec()之外,还有其他许多与进程交互的方
式。比如可以通过 kill()系统调用向进程发送信号,包括要求进程睡眠、终止或其
他有用的指令。实实上,整个信号子系统提供了一套丰富的向进程传递外部事件的途径。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。