Linux_进程
概念、创建、状态
文章目录
- Linux_进程
- 前言
- 一、进程基本概念
- 1、 进程
- 2、描述进程-PCB
- 二、对进程操作
- 1、组织进程
- 2、查看进程
- 3、通过系统调用获取进程标示符
- 4、★★★通过系统调用创建进程-fork初识
- 三、进程状态
- 1、进程状态查看
- 1. R状态(运行状态)
- 2. S、D状态(休眠状态 / 阻塞状态)
- 3. T状态
- 4. Z状态(zombie) && X状态(dead)
- 5. 孤儿进程
- 总结
前言
- 冯诺依曼体系结构
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。
截至目前,我们所认识的计算机,都是有一个个的硬件组件组成
- 输入单元:包括键盘, 鼠标,扫描仪, 写板等
- 中央处理器(CPU):含有运算器和控制器等
- 输出单元:显示器,打印机等
关于冯诺依曼:
- 这里的存储器指的是内存
- 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
- 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
- 一句话,所有设备都只能直接和内存打交道。
- 操作系统(Operator System)
概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
- 内核(进程管理,内存管理,文件管理,驱动管理)
- 其他程序(例如函数库,shell程序等等)
设计OS的目的
- 与硬件交互,管理所有的软硬件资源
- 为用户程序(应用程序)提供一个良好的执行环境
定位
- 在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件
如何理解 "管理"
- 管理的例子
- 描述被管理对象
- 组织被管理对象
站在用户的角度:
用户->用户操作接口->system call。
总结
计算机管理硬件
- 描述起来,用struct结构体
- 组织起来,用链表或其他高效的数据结构
先描述在组织
系统调用和库函数概念
- 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
- 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
↓ 对 进程 进行详解
一、进程基本概念
1、 进程
- 课本概念:程序的一个执行实例,正在执行的程序等
- 内核观点:担当分配系统资源(CPU时间,内存)的实体。
2、描述进程-PCB
- 程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
task_struct-PCB的一种
- 在Linux中描述进程的结构体叫做task_struct。
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
task_ struct内容分类
-
标示符: 描述本进程的唯一标示符,用来区别其他进程。
-
状态: 任务状态,退出代码,退出信号等。
-
优先级: 相对于其他进程的优先级。
-
程序计数器: 程序中即将被执行的下一条指令的地址。
-
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
-
上下文数据: 进程执行时处理器的寄存器中的数据[要加图CPU,寄存器]。
-
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
-
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
-
其他信息
二、对进程操作
1、组织进程
- 可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。
2、查看进程
- 进程的信息可以通过 /proc 系统文件夹查看
如:要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹。
- 大多数进程信息同样可以使用top和ps这些用户级工具来获取
运行如下查看进程
>#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
while(1)
{
sleep(1);
}
return 0;
}
3、通过系统调用获取进程标示符
-
进程id(PID)
-
父进程id(PPID)
4、★★★通过系统调用创建进程-fork初识
- 运行 man fork 认识fork
- fork有两个返回值
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
返回值
fork之后,子进程返回值为 0 ,父进程返回子进程的PID,如果创建进程失败,返回-1,并且错误码被设置。
代码如下
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 int main()
5 {
6 // int i = 10;
7 printf("我是一个进程,我的pid是:%d,我的父进程是:%d\n",getpid(),getppid());
8 sleep(4);fork();
9
10 while(1)
11 {
12 printf("我是一个进程,我的pid是:%d,我的父进程是:%d\n",getpid(),getppid());
13 sleep(1);
14 }
15 return 0;
16 }
~
可以看出在fork之前只是有父进程的,fork之后除了父进程又多了一个子进
- fork 之后通常要用 if 进行分流
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if(ret < 0)
{
perror("fork");
return 1;
}
else if(ret == 0)
{ //child
printf("I am child : %d!, ret: %d\n", getpid(), ret);
}
else
{ //father
printf("I am father : %d!, ret: %d\n", getpid(), ret);
}
sleep(1);
return 0;
}
1、fork做了什么事情?
2、为什么fork有2个返回值?
3、为什么forkd的两个返回值,会给父进程返回子进程pid,给子进程返回0?
4、fork之后父子进程谁先运行?
5、如何理解同一个变量,会有不用的值?
1、fork做了什么事情?
fork创建子进程,系统中会多一个子进程
1.以父进程为模板,为子进程创建PCB
2、但是你今天创建的子进程,是没有代码和数据的!
目前和父进程共享代码和数据!
所以fork之后,父子进程会执行一样的代码。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 int main()
5 {
6 // int i = 10;
7 printf("我是一个进程\n");
8 fork();
9
10 sleep(1);
11 printf("我也是一个进程,我看看我会被执行几次?\n");
12
13 sleep(1);
14 return 0;
15 }
A.fork之前的代码父子进程都可以看到吗? 答案是的!
B.那为什么子进程不从头开始执行代码呢?
为什么我们的程序从上往下按照顺序去执行,是因为在程序执行时,有一个pc指针/eip寄存器,当执行完当前指令的只有,这个eip会自动指向下一条指令
那么当eip指向的是fork后续的代码,eip也会被子进程继承
2、为什么fork有2个返回值?
由上可知,fork之后,代码父子进程代码共享,(fork之后后续代码都会被共享),那么return是代码吗,答案是的,所以return也要被共享
那么就有:
- 父进程被调度,就要执行return
- 父进程被调度,也要执行return
因此会有2个返回值
3、为什么forkd的两个返回值,会给父进程返回子进程pid,给子进程返回0?
例如在现实生活中父亲与儿女的数量对比一定是1:n (n>=1),1个父亲有若干个子女,假设n=3,对于父亲来说我有3个子女,我要知道我这3个子女都是谁,都叫什么名字,而对于子女来说,我只有这一个父亲。因此对于子进程来说当创建成功只有令返回值为0,只要代表我是子进程就可以了,但是对于父进程来说,返回值是子进程的PID,我要知道我的子进程到底是哪个。
4、fork之后父子进程谁先运行?
创建完成子进程,只是一个开始,创建完成子进程之后,系统的其他进程,父进程,和子进程,接下来要被调度执行的。当父子进程的PCB都被创建并在运行队列中排队的时候哪一个进程的PCB先被选择调度,那个进程就先运行!有OS自主决定,由各自PCB中的调度信息(时间片+优先级)+调度器算法共同决定。
5、如何理解同一个变量,会有不用的值?
进程的独立性,首先是表现在有各自的PCB,进程之间不会互相影响!代码本身是只读的,不会影响!但是数据父子是会修改的!
因此代码共享,数据各个进程必须想办法各自私有一份(发生了写时拷贝)。
三、进程状态
首先看一下Linux内核源码关于进程状态的定义都有哪些:
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
(interruptible sleep)) - D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
1、进程状态查看
ps aux / ps axj 命令
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
while(1)
{
//
}
return 0;
}
1. R状态(运行状态)
一旦这个进程启动了,命令行命令就不起作用了这就是一个前台进程(前台进程会有一个"+")例如上图中的
R
+
R^+
R+
./mypro & ,以后台进程的形式运行
并且后台进程ctrl+c是无法终止掉的!杀掉进程才能结束
2. S、D状态(休眠状态 / 阻塞状态)
代码如下
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
while(1)
{
int a= 0;
printf("pleasr ENTER#:\n");
scanf("%d",&a);
}
return 0;
}
//运行后,就是不输入
//
//由于不输入卡住了,这个卡住的状态就是阻塞状态
以上这种休眠属于 浅度睡眠
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
(interruptible sleep))- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
D状态也是休眠状态,深度睡眠,专门针对磁盘设计的,不可被杀掉,OS也没有资格
,
3. T状态
T(stopped)
查看进程信号的详细列表 kill -l
上图表示将进程暂停之后,进程状态的变化
S
+
S^+
S+->T。
我们为什么要暂停?,当进程访问软件资源的时候,可能暂时不让进程进行访问,就将进程设置为STOP
指令如下
kill -SIGSTOP PID
还有一个t(tracing stop):debug程序的时候,追踪程序,遇到断点,进程暂停了。
4. Z状态(zombie) && X状态(dead)
Z 僵尸状态
X 是一种瞬时的状态
代码如下:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t id = fork();
if(id<0) return 1;
else if(id == 0)
{
//子进程
int cnt = 5;
while(cnt)
{
printf("I am a child,run times:%d\n",cnt--);
sleep(1);
}
exit(0);//终止子进程
}
else{
//父进程
while(1)
{
printf("I am a father,running any time!\n");
sleep(1);
}
}
return 0;
}
可以看到,当子进程被终止后,其状态变更为Z状态。
5. 孤儿进程
>子进程的父进程直接退出了,子进程要被领养,变成孤儿进程
>(如果)
父进程被终止后,此时子进程被1号进程领养,变成孤儿进程。1号进程一般是OS
那么
A.为什么父进程退出后直接消失了,没有变成僵尸进程?
可看到父进程的父进程正是bash
是因为这个父进程直接被bash回收了,(能创建你,也能回收你)
B.为什么要领养?
如果不领养的话,那么这个进程在退出的时候,就没有人去回收它的PCB,就会造成内存泄漏
如果不领养就会造成大量的孤儿进程在退出之后变成Z状态,进而消耗内存资源
总结
对进程的查看,状态,创建过程的简单理解