【Linux学习】进程概念

简介: 【Linux学习】进程概念

一、进程的基本概念

进程(process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

简而言之:进程 = 可执行程序 + 该进程对应的内核数据结构

二、进程的描述-PCB

PCB(process control block)即进程控制块,进程信息就是被进程控制块的数据结构中,可以理解为进程属性的集合。在Linux中描述进程的结构体叫做task_struct,task_struct是PCB的一种,同时它也是是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。

task_struct的内容:

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
  • 上下文数据: 进程执行时处理器的寄存器中的数据。
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其他信息

【注意】以上内容都可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。

三、对进程的理解

1. 查看进程

  • 进程的信息可以通过 /proc 系统文件夹查看

  • 大多数进程信息同样可以使用top和ps这些用户级工具来获取
ps axj

2. 通过系统调用获取进程标识符

  • 进程id(PID),获取PID的系统函数:getpid()
  • 父进程id(PPID),获取PPID的系统函数:getppid()

利用man手册查看getpid/getppid

举个例子:

#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. 通过系统调用(fork)创建进程

使用man 手册认识fork:

由此我们可以对fork产生简单的认识:

Linux下系统调用可以用于创建子进程:fork(),当一个进程正在运行的时候,使用了fork()函数之后就会创建另一个进程。与一般函数不同的是,fork()函数会有两次返回值,一次返回给父进程(该返回值是子进程的PID(Process ID)),第二次返回是给子进程,其返回值为0。所以在调用该函数以后,我们需要通过返回值来判断当前的代码时父进程还是子进程在运行:


返回值大于0 -> 父进程在运行

返回值等于0 -> 子进程在运行

返回值小于0 -> 函数系统调用出错


【注意】通常系统调用出错的原因有两个:①已存在的系统进程已经太多;②该实际用户ID的进程总数已经超过了限制。


一个简单的例子:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    pid_t id = fork(); //子进程返回0,父进程返回 >0 。
    if(id == 0)
    {
        while(1)
        {
            printf("我是子进程,我的pid是:%d,我的父进程是:%d\n", getpid(), getppid());
            sleep(1);
        }
    }
    else 
    {
        while(1)
        {
            printf("我是父进程,我的pid是:%d,我的父进程是:%d\n", getpid(), getppid());
            sleep(1);
        }
    }
    return 0;
}


结果:

【注意】

  • 该示例代码中,使用了fork()函数了之后,下面通过pid的返回值来判断是父进程还是子进程在被调用,或者是系统调用失败(pid < 0 时)。其父子进程的运行顺序并不是一定的,这个系统的调度策略有关系。
  • 使用同一个id却产生了两种不同的结果,其中的原因在我们对进程地址空间了解之后自然就会明白。系统调用fork函数之后,父子进程会共享代码,一般都会执行后续代码,但是对于数据会各自开辟空间,各自一份,互不影响。

四、进程状态

1、进程状态转换

基本状态:

  • 就绪状态(ready):进程已经分配除了CPU之外的所有必要资源,只要再获得CPU资源就可以立即执行。
  • 执行状态(running):进程已经获得CPU资源,程序正在执行。
  • 阻塞状态(block):进程等待某种资源(非CPU资源),资源没有就绪时,进程需要在该资源的等待队列中排队,此时程序的代码没有运行,进程所处的状态就叫做阻塞状态。
  • 挂起状态(hang):当内存不足的时候,并且短期内程序不会被调度,如果程序的代码和数据依旧在内存中就会浪费内存空间,操作系统就会把该进程的代码和数据置换到磁盘中,这个过程称为挂起状态。

2. Linux内核源代码定义的进程状态

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。下面的状态在kernel源代码里定义:

/*
* 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):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

3. 进程状态的查看命令

ps aux / ps axj

4. 僵尸状态

僵尸状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程

僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态


一个变僵尸进程的例子:

#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
void test_zombies_process()
{
    pid_t id = fork();
      if (id == 0)
      {
         int cnt = 5;
         while(cnt--)
         {
            printf("我是子进程,我还剩下%d秒\n", cnt);
            sleep(1);
         }
         printf("我是子进程,我变成僵尸了\n");
         exit(0);
      }
      else
      {
          while(1)
          {
          printf("我是父进程\n");
          sleep(1);
          }
      }
}
int main()
{
  test_zombies_process();
  return 0;
}


僵尸进程危害:

  • 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态!
  • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护!
  • 当一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!

5. 孤儿进程

在操作系统领域中,孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

一个孤儿进程的例子:

void test_orphan_process()
{
    pid_t id = fork();
    if(id == 0)
    {
        while(1)
        {
            printf("我是子进程\n");
            sleep(1);
        }
    }
    else
    {
      int cnt = :wq5;
        while(cnt--)
        {
            printf("我是父进程,我还剩%d秒\n", cnt);
            sleep(1);
        }
        sleep(3);
        exit(0);
    }
}

当父进程死亡后,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。


五、进程优先级

1. 进程优先级的概念

由于系统中进程数量众多,而CPU资源比较少甚至只有一个,进程之间需要竞争来使用CPU。这时让一个比较重要、需要优先执行的进程去和其他进程竞争,显然是不合理的。为了更合理的分配CPU资源, 就有了进程优先级。优先级高的进程有优先执行的权利。此外,优先级还影响分配给进程的时间片长短。 重要的进程,应该分配多一些cpu时间片,好让其尽快完成任务。所有的进程都会有机会运行,但优先级高的进程会获取更多的cpu执行时间。配置进程优先级对多任务环境的Linux很有用,可以改善系统性能。


2. 优先级的查看

在linux系统中,用ps –l命令则会类似输出以下几个内容:

我们很容易会注意到其中的几个信息:

UID : 代表执行者的身份

PID : 代表这个进程的代号

PPID :代表这个进程是由哪个进程发展衍生而来的,即父进程的代号

PRI :代表这个进程可被执行的优先级,其值越小越早被执行

NI :代表这个进程的nice值


3. PRI 和 NI

PRI,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高;而NI就是我们所说的nice值,其表示进程可被执行的优先级的修正数值。

PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice 。这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。所以,调整进程优先级,在Linux下,就是调整进程nice值。nice 其取值范围是 -20 至 19 ,一共40个级别。

【注意】

需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。可以理解nice值是进程优先级的修正修正数据。

4. 修改进程的优先级

用top命令更改已存在进程的nice:

  • 先执行 top 指令
  • 进入top后按 【r】 ,输入进程PID,输入nice值

六、进程相关的其他概念

  • 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。
  • 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行。
  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。
目录
相关文章
|
6月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
259 67
|
5月前
|
NoSQL Linux 编译器
GDB符号表概念和在Linux下获取符号表的方法
通过掌握这些关于GDB符号表的知识,你可以更好地管理和理解你的程序,希望这些知识可以帮助你更有效地进行调试工作。
244 16
|
5月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
150 16
|
5月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
114 20
|
4月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
102 0
|
4月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
127 0
|
4月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
91 0
|
4月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
90 0
|
7月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
7月前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
251 4

热门文章

最新文章