Linux:浅析进程(二)

本文介绍了五种进程调度算法,包括时间片轮转、先来先服务等,并详细解析了Linux下的task_struct结构体,最后通过实例展示了僵尸进程与孤儿进程的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上篇博客浅析进程(一)中,提到了进程的一些概念,以及与进程有关的一些内容。程序在执行时,加载到内存,由操作系统进行描述,然后组织成双链表,进而管理。而这个过程中由操作系统描述后的内容被叫做PCB,而在Linux操作系统下,PCB是一个名字叫做 task_struct的结构体,那这个结构体内到底是什么东西呢?


一、进程的调度算法
进程的调度算法是指:根据系统的资源分配策略所规定的资源分配算法。

进程调度算法有五种:
1. 时间片轮转调度算法(RR):给每个进程固定的执行时间,根据进程到达的先后顺序让进程在单位时间片内执行,执行完成后便调度下一个进程执行,时间片轮转调度部考虑进程等待时间和执行时间,属于抢占式调度。优点:兼顾长短进程。缺点:平均等待的时间过长,上下文切换较费时间。适用于分时系统。(分时系统:多个用户分享使用同一台计算机,多个程序分时共享软件和硬件资源。)

2.先来先服务调度算法(FCFS): 根据进程到达的先后顺序执行进程,不考虑等待时间和执行时间,会产生饥饿现象。属于非抢占式调度。优点:公平,实现简单。缺点:不利于短进程工作。(饥饿现象:等待时间过长给进程的推进和响应造成明显影响叫做饥饿现象)

3.优先级调度算法(HPF):在进程等待队列中选择优先级最高的来执行。

4.多级反馈队列调度算法:将时间片轮转与优先级调度结合,把进程按优先级分成不同的队列,先按优先级调度,优先级相同的按时间片轮转。优点:可以兼顾长短进程,有较好的响应时间,可行性强。适用于各种作业环境。

5.高响应比优先调度算法:根据“响应比 = (进程执行时间 + 进程等待时间)/ 进程执行时间”这个公式得到响应比来进行调度。高响应比优先算法在等待时间相同的情况下,进程执行的时间越短,其响应比越高,满足段任务优先, 同时响应比会随着等待时间增加而变大,优先级会提高,能够避免饥饿现象。优点:兼顾长短进程。缺点:计算机响应开销比较大。适用于批处理系统。

这里说一下抢占式调度与非抢占式调度的区别。
抢占式调度:允许将逻辑上可继续运行的进程,在运行过程中将其暂停,从而调度其他的进程。可以防止单一进程长时间独占CPU系统开销大。
非抢占式调度:让进程运行直到结束或阻塞的调度方式,容易实现,适合专用系统,不适合通用系统 。


二、task_struct结构体

我们知道,在Linux操作系统下,进程控制块即PCB是task_struct结构体,那这个结构体内有什么内容呢?

利用find命令,我们在usr的路径下最终找到了包含task_struct结构体的文件。
这里写图片描述
这里写图片描述

struct task_struct 
{
 //说明了该进程是否可以执行,还是可中断等信息
    volatile long state;  
 //Flage 是进程号,在调用fork()时给出
 unsigned long flags;  
 //进程上是否有待处理的信号
 int sigpending;   
 //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
 mm_segment_t addr_limit; //0-0xBFFFFFFF for user-thead  
      //0-0xFFFFFFFF for kernel-thread

 //调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
 volatile long need_resched;
 //锁深度
 int lock_depth;  
 //进程的基本时间片
 long nice;       
 //进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER
 unsigned long policy;
 //进程内存管理信息
 struct mm_struct *mm; 

 int processor;
 //若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新
 unsigned long cpus_runnable, cpus_allowed;
 //指向运行队列的指针
 struct list_head run_list; 
 //进程的睡眠时间
 unsigned long sleep_time;  
 //用于将系统中所有的进程连成一个双向循环链表, 其根是init_task
 struct task_struct *next_task, *prev_task;
 struct mm_struct *active_mm;
 struct list_head local_pages;       //指向本地页面      
 unsigned int allocation_order, nr_local_pages;
 struct linux_binfmt *binfmt;  //进程所运行的可执行文件的格式
 int exit_code, exit_signal;
 int pdeath_signal;     //父进程终止是向子进程发送的信号
 unsigned long personality;
 //Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
 int did_exec:1; 
 pid_t pid;    //进程标识符,用来代表一个进程
 pid_t pgrp;   //进程组标识,表示进程所属的进程组
 pid_t tty_old_pgrp;  //进程控制终端所在的组标识
 pid_t session;  //进程的会话标识
 pid_t tgid;
 int leader;     //表示进程是否为会话主管
 struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
 struct list_head thread_group;   //线程链表
 struct task_struct *pidhash_next; //用于将进程链入HASH表
 struct task_struct **pidhash_pprev;
 wait_queue_head_t wait_chldexit;  //供wait4()使用
 struct completion *vfork_done;  //供vfork() 使用
 unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值
 //it_real_value,it_real_incr用于REAL定时器,单位为jiffies, 系统根据it_real_value
 //设置定时器的第一个终止时间. 在定时器到期时,向进程发送SIGALRM信号,同时根据
 //it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。
 //当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送
 //信号SIGPROF,并根据it_prof_incr重置时间.
 //it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种
 //状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据
 //it_virt_incr重置初值。
 unsigned long it_real_value, it_prof_value, it_virt_value;
 unsigned long it_real_incr, it_prof_incr, it_virt_value;
 struct timer_list real_timer;   //指向实时定时器的指针
 struct tms times;      //记录进程消耗的时间
 unsigned long start_time;  //进程创建的时间
 //记录进程在每个CPU上所消耗的用户态时间和核心态时间
 long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; 
 //内存缺页和交换信息:
 //min_flt, maj_flt累计进程的次缺页数(Copy on Write页和匿名页)和主缺页数(从映射文件或交换
 //设备读入的页面数); nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
 //cmin_flt, cmaj_flt, cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
 //在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
 unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
 int swappable:1; //表示进程的虚拟地址空间是否允许换出
 //进程认证信息
 //uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid
 //euid,egid为有效uid,gid
 //fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件
 //系统的访问权限时使用他们。
 //suid,sgid为备份uid,gid
 uid_t uid,euid,suid,fsuid;
 gid_t gid,egid,sgid,fsgid;
 int ngroups; //记录进程在多少个用户组中
 gid_t groups[NGROUPS]; //记录进程所在的组
 //进程的权能,分别是有效位集合,继承位集合,允许位集合
 kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
 int keep_capabilities:1;
 struct user_struct *user;
 struct rlimit rlim[RLIM_NLIMITS];  //与进程相关的资源限制信息
 unsigned short used_math;   //是否使用FPU
 char comm[16];   //进程正在运行的可执行文件名
 //文件系统信息
 int link_count, total_link_count;
 //NULL if no tty 进程所在的控制终端,如果不需要控制终端,则该指针为空
 struct tty_struct *tty;
 unsigned int locks;
 //进程间通信信息
 struct sem_undo *semundo;  //进程在信号灯上的所有undo操作
 struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
 //进程的CPU状态,切换时,要保存到停止进程的task_struct中
 struct thread_struct thread;
   //文件系统信息
 struct fs_struct *fs;
   //打开文件信息
 struct files_struct *files;
   //信号处理函数
 spinlock_t sigmask_lock;
 struct signal_struct *sig; //信号处理函数
 sigset_t blocked;  //进程当前要阻塞的信号,每个信号对应一位
 struct sigpending pending;  //进程上是否有待处理的信号
 unsigned long sas_ss_sp;
 size_t sas_ss_size;
 int (*notifier)(void *priv);
 void *notifier_data;
 sigset_t *notifier_mask;
 u32 parent_exec_id;
 u32 self_exec_id;
 spinlock_t alloc_lock;
 void *journal_info;
 };

注:此task_struct来源于网络。

三、孤儿进程与僵尸进程

在上篇博客中提到了僵尸进程与孤儿进程,在这里来模拟实现以下僵尸进程与孤儿进程。

僵尸进程:

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

int main()
{
    int ret = fork();
    while(1)
    {
        if(ret)
        {
            printf("father:pid:%d, ppid:%d\n",getpid(),getppid());
            sleep(1);
        }
        if(!ret)
        {
            printf("child:pid:%d, ppid:%d\n",getpid(),getppid());
            sleep(1);
        }
    }
    return 0;

我们先利用fork()来创建子进程父进程,父子进程一直死循环运行,然后利用kill来杀死这个子进程,此时子进程便成了僵尸进程。
这里写图片描述

孤儿进程:
通用利用上个代码,我们杀死父进程,然后再来观察。
这里写图片描述
这样我们就看到了,在父进程结束后,孤儿进程由一号进程领养,变为其父进程。


欢迎大家共同讨论,如有错误及时联系作者指出,并改正。谢谢大家!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值