抽象:进程
1、进程的定义:操作系统为正在运行的程序提供抽象,就是所谓的进程。
2、时分共享和空分共享:
- 时分共享:通过允许资源由一个实体使用一小段时间,然后由另一个实体使用一小段时间,如此下去,所谓的资源(例如,CPU 或网络链接)可以被许多人共享。
- 空分共享:资源在空间上被划分给希望使用它的人,磁盘空间自然是一个空分共享资源。
3、进程API:
- 创建(create)
- 销毁(destroy)
- 等待(wait)
- 其他控制(miscellaneous control)
- 状态(statu)
4、进程创建的某些细节:
操作系统运行程序必须做的第一件事是将代码和所有静态数据(例如初始化变量)加载(load)到内存中,加载到进程的地址空间中。现代操作系统惰性(lazily)执行该过程,即仅在程序执行期间需要加载的代码或数据片段,才会加载。
C程序:
- 使用栈存放局部变量、函数参数和返回地址。
- 堆用于显式请求的动态分配数据。程序通过调用malloc()来请求这样的空间,并通过调用free()来明确地释放它。数据结构(如链表、散列表、树和其他有趣的数据结构)需要堆。
5、进程状态:
- 运行(running)
- 就绪(ready)
- 阻塞(blocked)
7、进程控制块(Process Control Block,PCB),也称进程列表:是一个包含每个进程信息的C 结构。
倒叙:进程API
1、fork()系统调用:
(1 系统调用fork()用于创建新进程。
(2 子进程不会从main()函数开始执行。而是直接从fork()系统调用返回,就好像是它自己调用了fork()。
(3 子进程并非完全拷贝父进程。
代码:
// p1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
//打印父进程pid
printf("hello world (pid:%d)\n", (int) getpid());
//fork()在父进程中返回子进程pid,在子进程中返回0
int rc = fork();
if (rc < 0) {
fprintf(stderr, "fork failed\n");
exit(-1);
} else if (rc == 0) {
//子进程执行
printf("hello, I am child (pid:%d)\n", (int) getpid());
} else {
// 父进程执行
printf("hello, I am parent (pid:%d)\n", (int) getpid());
}
return 0;
}
执行结果:
zk@ubuntu:~/Desktop/OS/5$ gcc -o p1 p1.c
zk@ubuntu:~/Desktop/OS/5$ ./p1
hello world (pid:11990)
hello, I am parent (pid:11990)
hello, I am child (pid:11991)
可见在我的机器里,父进程先执行,子进程后执行。
2、wait()系统调用:可等待子进程执行完毕,返回父进程执行。
代码:
// p2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
//打印父进程pid
printf("hello world (pid:%d)\n", (int) getpid());
//fork()在父进程中返回子进程pid,在子进程中返回0
int rc = fork();
if (rc < 0) {
fprintf(stderr, "fork failed\n");
exit(-1);
} else if (rc == 0) {
//子进程执行
printf("hello, I am child (pid:%d)\n", (int) getpid());
} else {
int wc = wait(NULL);
// 父进程执行
printf("hello, I am parent (pid:%d)\n", (int) getpid());
}
return 0;
}
执行结果:
zk@ubuntu:~/Desktop/OS/5$ gcc p2.c -o p2
zk@ubuntu:~/Desktop/OS/5$ ./p2
hello world (pid:1892)
hello, I am child (pid:1893)
hello, I am parent (pid:1892)
可见子进程先执行。
3、exec()系统调用:这个系统调用可以让子进程执行与父进程不同的程序。
代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[]){
//打印父进程pid
printf("hello world (pid:%d)\n", (int) getpid());
//fork()在父进程中返回子进程pid,在子进程中返回0
int rc = fork();
if (rc < 0) {
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
//子进程执行
printf("hello, I am child (pid:%d)\n", (int) getpid());
char *myargs[2];
myargs[0] = strdup("pwd");
myargs[1] = NULL;
execvp(myargs[0], myargs);
printf("this shouldn't print out");
} else {
int wc = wait(NULL);
// 父进程执行
printf("hello, I am parent (pid:%d)\n", (int) getpid());
}
return 0;
}
执行结果:
zk@ubuntu:~/Desktop/OS/5$ ./p3
hello world (pid:2024)
hello, I am child (pid:2025)
/home/zk/Desktop/OS/5
hello, I am parent (pid:2024)
exec()会从可执行程序中加载代码和静态数据,并用它覆写自己的代码段(以及静态数据),堆、栈及其他内存空间也会被重新初始化。
子进程执行exec()之后,几乎就像p3.c 从未运行过一样。对exec()的成功调用永远我会返回。
4、这种分离fork()及exec()的做法在构建UNIX shell 的时候非常有用,因为这给了shell 在fork 之后exec 之前运行代码的机会,这些代码可以允许运行新程序前改变环境,从而让一系列有趣的功能很容易实现。
UNIX 管道也是用类似的方式实现的,但用的是pipe()系统调用。
受限直接执行
1、用户模式和内核模式:
- 用户模式(user mode)下,应用程序不能完全访问硬件资源。运行的代码会受到限制。运行时,进程不能发出I/O 请求。
- 内核模式(kernel mode)下,操作系统可以访问机器的全部资源。
- 操作系统还提供了陷入(trap)内核和从陷阱返回(return-from-trap)到用户模式程序的特别说明,以及一些指令。
2、操作系统回收CPU使用权的方法(当进程恶意无限循环时):利用时钟中断重新获得控制权。
3、上下文切换:操作系统为当前正在执行的进程保存一些寄存器的值(例如,到它的内核栈),并为即将执行的进程恢复一些寄存器的值(从它的内核栈)。