Linux 进程概论

1. 了解冯诺依曼体系结构

自行了解 不做赘述。

2. 什么是操作系统 如何了解它

操作系统最主要的作用是对一台计算机软硬件资源的管理。

操作系统主要进行四个方面的管理:
文件管理, 进程管理, 内存管理, 驱动管理(由内核进行)
操作系统还包括其他程序 比如库函数 、shell(命令行解释器)程序。

操作系统的目的:
1.与硬件交互 管理系统的软硬件资源
2.为用户程序提供一个良好的执行环境。

操作系统是一款纯正的”搞管理“的软件

必须认识到一个普遍真理即——-描述是管理的前提
要有效管理一个被管理的对象必不可少的要描述其性质。
(例如学校的教务管理系统中有每位同学的学生信息)
有了其性质的描述以后再对其进行组织。

如何描述 ???——–通过struct描述复杂的性质。
如何组织???———通过链表 树 等数据结构进行有效组织。

计算机的层次性

这里写图片描述

理解区分库函数与系统调用

  1. 在开发角度,为了保护操作系统安全。操作系统对外表现为一个被包裹起来的整体,以向外暴露一个个接口的方式提供功能。这些接口叫系统调用。
  2. 系统调用功能比较基础, 对使用它的用户对操作系统知识的要求较高,鉴于此 开发者们开发了库函数对系统调用进行封装 。有了库,就更有利于更上层的用户进行开发。

????为什么有了系统调用还要有库函数
1. 库函数扩展了系统调用的功能。
2. 库函数提供了不同操作系统平台下的可移植性保障。
3. 库函数的使用更方便。

初探进程

  1. 进程是程序执行的实例、是正在执行的程序。
  2. 从内核的角度来看 是担当分配系统资源(CPU 、内存)的实体。

进程的两个基本元素是进程代码 和与这些代码关联的数据。
进程是一种动态描述 ,不是所有的进程都在运行。(进程在内存中因调度策略和调度要求,会处于各种不同状态)

进程描述

一个进程的所有属性描述信息被放在叫进程控制块(PCB)的结构体中
Linux上叫task_struct。
Linux上task_struct 存放着它所对应的进程的所有属性信息。
task_struck 和进程一一对应 ,一旦磁盘上的可执行程序被执行,它的部分或全部代码被加载如内存 并在内存中生成一个用于描述进程属性的task_struct。

task_struck所包含比较重要的内容:
1. 进程标识符:为了区别其他进程,描述本进程的唯一标识符。
2. 状态:任务状态、退出代码、退出信号等。
3. 优先级:当前进程相对于其他进程得到系统资源的先后顺序级别(主要指CPU资源)
4. 程序计数器 —进程中即将被执行的下一条指令(代码)的地址(相对于CPU中的PC指针)
5. 内存指针:通过task_struct要找到与其对应的进程的代码和数据就要有内存指针,内存指针也指向与其他进程共享的内存块。
6. 上下文数据:进程执行时CPU寄存器中的数据。
7. I/O状态信息:显示的I/O请求、分配给进程的I/O设备和被进程使用的文件列表。
8. 记账信息: 处理器处理时间总和、使用的时钟数总和、记账号等等。。。。。

~~~~解释记账信息作用:
从记账信息可以看出某个进程I/O操作多 就让他高优先级 让CPU先处理它 好让其他进程接下来执行少被I/O干扰。
如果某个进程计算操作多 就降低它的优先级 确保它不长期占用CPU。

图解PCB(task_struct)
这里写图片描述

图解进程上下文信息:
这里写图片描述

task_struct结构体的全部信息:

http://blog.csdn.net/x__016meliorem/article/details/70060305

查看进程状态

可以通过 ps -aux | grep (可执行程序)
ps -axj | grep (可执行程序)
这两条命令 ps -aux 或 axj 是显示进程状态 | 是管道 grep是行过滤工具 后加可执行程序名就可以列出关心的进程状态了。

还有一种方法可以用来查看进程 ———/proc 系统文件夹

该文件夹下的文件夹以进程pid来命名 比如要查1号进程的信息 进入 /proc/1文件夹下查看。
这里写图片描述

获取进程ID函数

getpid();//获取当前进程ID
getppid();//获取当前进程父进程ID
两个函数的本质是 在PCB(task_struct)中获取 pid 和ppid这两个成员变量。

子进程的创建
fork();
该函数的特点是有两个返回值 一般要用if else 语句分开
返回值大于0时执行的是父进程代码,
返回值等于零是执行的是子进程代码,
父进程中看到的fork()返回的是所创建的自进程的pid(标识这是创建的哪个子进程),
子进程中fork()返回0

如果返回值小于0 说明fork()失败。
fork 失败的原因: 1.内存不足 2.进程的最大数量有限制 3.操作系统正在重启

#include <stdio.h>
#include<unistd.h>


int g_val = 100;

int main()
{
    int ret = fork();
    if(ret > 0){
        sleep(2);
        printf("father %d,%p, g_val=%d\n",getpid(),&g_val,g_val);
    }else if(ret == 0){
        g_val = 200;
        sleep(1);
        printf("child %d, %p,g_val=%d\n",getpid(),&g_val,g_val);
    }else{
        perror("fork");
    }

    return 0;
}

后面还要谈fork()

进程状态

Linux内核源码为进程定义了如下几种状态。

R运行状态: 进程不是正在运行就是在运行队列中。
S睡眠状态: 进程正在等待某件事的完成 这里的睡眠是可中断的。
D磁盘休眠状态: 不可中断的睡眠状态 该状态的进程应该正等待I/O的结束。
T状态: 停止状态 我们可以发送SIGSTOP信号终止进程 也可以发送SIGCONT让进程继续。
kill -SIGSTOP (PID)
kill -SIGCONT (PID)
Z 状态: 僵尸状态 形成僵尸进程的原因是 子进程退出时父进程还在运行且并未回收其退出代码。 这是子进程以终止状态保持在内核进程表中,并且会一直等待父进程回收其状态。

——僵尸进程的危害:

进程的退出状态只要不被回收就必须被维持下去,它要告诉它的父进程自己的任务完成的如何 。
进程退出状态也属于进程的基本信息 在PCB中用数据保存着 PCB就要一直被维护 如果系统中有大量子进程被创建 这些结构体都要在内存中占空间长时间占用大量资源。

僵尸进程代码:

#include<stdio.h>
#include<unistd.h>

int main()
{
    int ret = fork();
    if(ret > 0){//ret = child pid
        //father 
        printf("I am father : %d\n",getpid());
        while(1){sleep(1);}
//子进程已经退出 父进程没有回收子进程状态且一直没有退出。
    }else if(ret == 0){// 
        //child
        printf("I am child :%d\n",getpid());
    }else if(ret < 0){//fork 失败的原因  1.内存不足   2.进程的最大数量有限制   3.操作系统正在重启
        perror("fork");//向标准输出打印失败信息
    }
    return 0;
}

这里写图片描述

孤儿进程

如果 父进程先退出 子进程没有退出
等到子进程退出时父进程已经无法回收子进程推出状态 这时子进程退出状态可以让 1 号进程(init进程)回收 init进程是操作系统启动时创建的进程 其它进程皆由它创建。

这时的子进程叫孤儿进程。

#include <stdio.h>
#include<unistd.h>

int main()
{
    int ret = fork();
    if(ret > 0){
        //father
        printf("father %d\n",getpid());
       // sleep(3);
//父进程已经退出
    }else if(ret == 0){
        //child

            printf("child: %d. %d, \n",getpid(), getppid());
            sleep(10);
//子进程不退出
        }
    }else{
        perror("fork");
    }
    return 0;
}

可以通过 kill -9 (ppid)命令 将父进程杀死的方式使僵尸进程被init进程回收 这是孤儿进程的父进程id会被标识为1.
但是这种方法很不好 实际上很多时候父进程不可以随意被杀死。

程序地址空间

地址空间图:
这里写图片描述

—–代码段可不可以修改?
可以 比如调试器加断点 游戏外挂。

图中由下往上是从低地址到高地址

下面一段测试代码:

#include <stdio.h>
#include<unistd.h>

int g_val = 100;
int main()
{
    int ret = fork();
    if(ret > 0){
        sleep(2);
        printf("father %d,%p, g_val=%d\n",getpid(),&g_val,g_val);
    }else if(ret == 0){
        g_val = 200;
        sleep(1);
        printf("child %d, %p,g_val=%d\n",getpid(),&g_val,g_val);
    }else{
        perror("fork");
    }

    return 0;
}

这段代码输出的父子进程g_val的地址是一样的
但父进程g_val没变是100 子进程g_val却是200.

为什么 代码输出的%p 是地址空间的地址 不是物理内存。
实际上父子进程的g_val 在真正的物理内存中是两份。

OS负责将虚拟地址转化成物理地址 用户是看不到物理地址的

虚拟地址与物理地址

图解:
这里写图片描述

为什么要有虚拟地址空间?
1.如果直接使用物理地址 一个进程有BUG 内存越界会影响另一个进程。
2.需要在进行时把全部内存地址空间加载到内存中占用空间大
3. 内存间切换不方便。

现在有页表 不同的进程使用不同的页表 进程的地址空间通过页表映射到物理内存上 各个进程通过页表可以将需要的一部分的地址空间加载人物理地址 这样可以加快内存切换效率

各个进程在自己的地址空间内对应的物理内存中运行   不影响其他进程 
CPU中的段寄存器 使进程的物理地址隔离起来 容易找到内存越界问题。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章