目录
为了更好的阐述进程,我们先来对冯诺依曼体系结构和操作及系统进行一个简单的介绍
1、冯诺依曼结构体系
冯.诺依曼体系结构是现代计算机的基础,现在大多计算机仍是冯.诺依曼计算机的组织结构,其结构大致如下:
基本组成:
输入设备:输入设备是向计算机输入数据和信息的终端设备。
输出设备:用来接收计算机输出信息的终端设备。
存储器:这里指的是内存,用来存放电脑运行中的原始数据、中间结果以及指示电脑工作的数据。
运算器:计算机中执行各种算术和逻辑运算操作。
控制器:完成协调和指挥整个计算机系统的操作
控制器 + 运算器 + 其他 = CPU
结论:
1、从上图中我们可以看到CPU在数据层面上并不直接和输入输出设备直接打交道,而只和内存直接打交道,因为CPU和外设速度相差太多了,所以用内存来缓解速度不匹配的问题。
那么程序运行为什么先得加载到内存呢?因为CPU要执行代码访问数据,只能从内存中读取。
我们可以按照上面来试着解释一下发送和接收消息(不考虑其他的结构):
2、操作系统
操作系统是一个进行软硬件资源管理的软件
1、计算机怎么对软硬件进行管理
计算机软硬件体系结构:
举个生活中的例子:
你进了一家公司,你连CEO的面都没见过几次,但是他却可以做相应的决策把公司的员工给管理起来,如果是做决策就得有相关的依据,这里的依据就是你的姓名、工号、部门号等你在公司所具备得数据,所以对员工的管理就转换到了对数据的管理,那么有这么多的员工应该怎么管理呢?可以通过一定的方式,将你们的数据给描述成一个特定的模板,然后再通过某种方式组织起来统一进行管理。
操作系统也是通过这种方式对软硬件进行管理的---将被管理对象描述成结构体+通过某种数据结构组织起来
3、进程概念
基本理解:加载到内存中的程序
内核观点:内核数据结构 + 进程对应的磁盘代码
当多个进程被加载到内存中的时候,操作系统就要对它们进行管理,那么我们就得先对加载进来的程序进行描述(PCB)并组织(用特定的数据结构)起来,图中使用的是链表。
PCB:
Linux操作系统下的PCB是: task_struct,task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
task_ struct内容分类:
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
3.1查看进程
windows中打开任务管理器可以看到进程:
在Linux系统下查看进程
ps命令
ps:一次性给出当前系统中进程状态
a:显示一个终端的所有进程,除会话引线外;
u:显示进程的归属用户及内存的使用情况;
x:显示没有控制终端的进程;
-l:长格式显示更加详细的信息;
-e:显示所有进程;
2、通过 /proc 系统文件夹查看
注:pid是用来标识一个进程唯一性的,类似于我们的身份证一样
top 命令
top:动态地持续监听进程地运行状态
-d 秒数:指定 top 命令每隔几秒更新。默认是 3 秒;
-b:使用批处理模式输出。一般和"-n"选项合用,用于把 top 命令重定向到文件中;
-n 次数:指定 top 命令执行的次数。一般和"-"选项合用;
-p 进程PID:仅查看指定 ID 的进程;
-s:使 top 命令在安全模式中运行,避免在交互模式中出现错误;
-u 用户名:只监听某个用户的进程;
通过系统调用获取进程标示符
进程id(PID)
父进程id(PPID)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
3.2进程状态
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)
下面的状态在kernel源代码里定义:
static const char * const task_state_array[] = {
"R (running)", /* 0 */运行状态
"S (sleeping)", /* 1 */浅度睡眠状态
"D (disk sleep)", /* 2 */深度睡眠状态
"T (stopped)", /* 4 */常规的暂停状态
"T (tracing stop)", /* 8 */表示进程正在被追踪,linux下显示t(调试)
"X (dead)", /* 16 */死亡状态
"Z (zombie)", /* 32 */僵尸状态
};
R运行状态(running):并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠)。
D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的
进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可
以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
Z僵死状态(Zombies):是一个比较特殊的状态。当进程退出,并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
3.3僵尸进程
1、僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
2、僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
3、僵尸进程处于僵死状态,资源并没有完全被释放,会造内存成泄漏的问题。并且由于操作系统所能创建的最大进程数量是有限的(进程号被大量占用)会导致新的进程无法创建
代码测试:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
//创建子进程
int pid = fork();
if (pid < 0) {
perror("fork");
return -1;
}
else if (pid == 0) {
//子进程
printf("我是子进程,我先退出啦,我的id是:%d\n", getpid());
exit(0);
}
else {
//父进程
printf("我是父进程,我还没退出,我的id是:%d\n", getpid());
sleep(30);
}
return 0;
}
运行结果:子进程的状态变成了Z,父进程依旧是S
3.4孤儿进程
父进程先退出,子进程就称之为“孤儿进程”,并且子进程会被操作系统领养。如果是前台进程创建的子进程,在其成为孤儿进程后,会自动变成后台进程。
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
//创建子进程
int pid = fork();
if (pid < 0) {
perror("fork");
return -1;
}
else if (pid == 0) {
//子进程
printf("我是子进程,我的id是:%d\n", getpid());
sleep(30);
exit(0);
}
else {
//父进程
printf("我是父进程,我的id是:%d\n", getpid());
}
return 0;
}
注: pid为1的默认为操作系统