[!quote] 操作系统
- 操作系统是虚拟机:操作系统将物理资源【
处理器,内存,磁盘】转换为更通用,更强大,更易于使用的虚拟形式- 操作系统是标准库:操作系统提供了 API 来供使用者调用,以此来运行程序,访问内存和设备,并进行其他相关操作
- 操作系统是资源管理器:操作系统虚拟化让许多程序并发运行【
从而共享 CPU】,让许多程序可以同时访问自己的指令和数据【从而共享内存】,让许多程序访问设备【从而共享磁盘】
进程
[!quote] 进程 进程 就是程序的一次独立运行。一个进程在执行过程中会访问和影响计算机系统的不同部分【
内存、CPU、磁盘 ……】,这些部分的状态和内容会随着进程的执行而改变,而这些变化可以被记录下来并概括为一个进程
启动进程之前,操作系统做了什么 ?
- 将代码和静态数据加载到内存中
- 为程序的运行时的栈分配一些内存,需要时也会为程序的堆分配内存
- 执行与 IO 设置相关的其他工作
进程的状态
![[Excalidraw/计算机/四大件/操作系统 Draw.md#^group=Rtlv2f-IRxbCmA8eWSrGi|385]]
- 初始:进程在创建时处于的状态
- 运行:进程正在处理器上运行,这意味着它正在执行指令
- 就绪:进程已准备好运行,但由于某种原因,操作系统选择不在此时运行
- 阻塞:进程执行某种操作时,需要等待其他事件的发生,才会准备运行
- 最终:进程处于已退出但尚未清理的状态
进程 API
fork() 系统调用
int main(int argc, char *argv[]) {
printf("hello world (pid:%d)\n", (int)getpid());
int rc = fork();
if (rc < 0) { // fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child (new process)
printf("hello, I am child (pid:%d)\n", (int)getpid());
} else { // parent goes down this path (main)
printf("hello, I am parent of %d (pid:%d)\n",rc, (int)getpid());
}
return 0;
}
hello world (pid:29146) //pid是进程描述符
hello, I am parent of 29147 (pid:29146) //在这里父进程先运行完
hello, I am child (pid:29147)当一个进程调用 fork() 系统调用时,操作系统会复制该进程的执行环境(包括内存空间、寄存器状态、上下文等),并为其创建一个新的进程。这个新进程和原进程几乎完全一样,它们的代码、数据和堆栈等都是相同的。 此外,子进程和父进程还有一些细微的差别【例如子进程中的某些变量可能与父进程不同,这是因为子进程和父进程是独立的进程,它们各自都有自己的内存空间和数据结构】
在这个过程中,子进程不会从 main() 函数开始执行,而是从 fork() 系统调用之后的代码开始执行。这是因为子进程会复制父进程的执行环境,包括程序计数器等信息,因此它会从 fork() 系统调用之后的代码开始执行。 因此,在原进程和子进程中,printf() 函数只会被调用一次,输出一条 "hello world" 的信息。
原进程和新进程都会从 fork() 系统调用中返回,子进程会返回 0,而父进程会返回子进程的进程 ID^1
wait() 系统调用
int main(int argc, char *argv[])
{
printf("hello world (pid:%d)\n", (int)getpid());
int rc = fork();
if (rc < 0) { // fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child (new process)
printf("hello, I am child (pid:%d)\n", (int)getpid());
} else {
int wc = wait(NULL);
printf("hello, I am parent of %d (wc:%d) (pid:%d)\n", rc, wc, (int)getpid());
}
return 0;
}
hello world (pid:29266)
hello, I am child (pid:29267)
hello, I am parent of 29267 (wc:29267) (pid:29266)这里其实是 有可能父进程先完成的【当父进程碰巧先运行,那么会马上调用 wait(),之后运行子进程,但是在子进程还未运行 printf() 时,wait() 返回了,接着父进程继续运行直到结束】
exec() 系统调用
int main(int argc, char *argv[]) {
printf("hello world (pid:%d)\n", (int)getpid());
int rc = fork();
if (rc < 0) { // fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child (new process)
printf("hello, I am child (pid:%d)\n", (int)getpid());
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p3.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
execvp(myargs[0], myargs); // 运行字符计数程序
printf("this shouldn’t print out");
}else { // parent goes down this path (main)
int wc = wait(NULL);
printf("hello, I am parent of %d (wc:%d) (pid:%d)\n", rc, wc, (int)getpid());
}
return 0;
}给定可执行程序的名称(如 wc)及需要的参数(如 p3.c)后,exec() 会从可执行程序中加载代码和静态数据,并用它覆写自己的代码段,静态数据,堆、栈及其他内存空间,它们都会被重新初始化。 然后操作系统就执行该程序,将参数通过 argv 传递给该进程 因此,它并没有创建新进程,而是直接将当前运行的程序(以前的 p3)替换为不同的运行程序(wc) 子进程执行 exec() 之后,几乎就像 p3.c 从未运行过一样。对 exec() 的成功调用永远不会返回