通过打印进程控制块中的信息深刻认识进程

一、通过top命令可以看到进程的相关信息。

在终端执行top指令:

在这里插入图片描述

上述参数含义说明:

参数含义
10:13:26表示当前时间
up 3 min系统运行时间
1 users当前登录用户数
load average: 0.73, 1.00, 0.47系统负载,即任务队列的平均长度。 三个数值分别为 1分钟、5分钟、15分钟前到现在的平均值。
total进程总数
running正在运行的进程数
sleeping睡眠的进程数
stopped停止的进程数
zombie僵尸进程数
%CPU所占CPU比例
us用户空间占用CPU百分比
sy内核空间占用CPU百分比
ni用户进程空间内改变过优先级的进程占用CPU百分比
id空闲CPU百分比
wa等待输入输出的CPU时间百分比
hi硬中断占用CPU的百分比
si软中断占用CPU的百分比
st用于有虚拟cpu的情况,用来指示被虚拟机偷掉的cpu时间
KiB Mem物理内存总量
used使用的物理内存总量
free空闲内存总量
buff/cache用作内核缓存的内存量
KiB Swap交换区总量
avail Mem代表可用于进程下一次分配的物理内存数量
PID进程id
USER进程所有者的用户名
PR优先级
NInice值 负值表示高优先级,正值表示低优先级
VIRT进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
RES进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
SHR共享内存大小,单位kb
S进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程
%CPU上次更新到现在的CPU时间占用百分比
%MEM进程使用的物理内存百分比
TIME+进程使用的CPU时间总计,单位1/100秒
COMMAND命令名/命令行

二、通过打印task_struct中的字段,可以看到更多的信息,请打印至少10个字段的信息,截图,并给出查看源代码的相关源代码,说明自己对进程控制块的深刻认识。

(一)代码运行

task_struct.c代码:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>   		//task结构体
#include <linux/fdtable.h>		//files
#include <linux/init_task.h>
#include <linux/fs_struct.h>

MODULE_LICENSE("GPL");  //许可证
 
//入口函数
static int __init print_pcb(void)    //init宏是由init.h文件所支持
{
        struct task_struct *task,*p;
        struct list_head *pos;   //双向链表
        int count=0;          //统计当前系统进程一共有多少个
 
        printk("begin...\n");
 
        //对链表遍历时,希望从第一个开始
        task=&init_task;  //指向0号进程pcb
 
        list_for_each(pos,&task->tasks) //遍历操作,使用pos指向,传入的参数task指向tasks字段.0号进程的tasks进行遍历。tasks将所有的进程连在一块。
        {
                p=list_entry(pos,struct task_struct,tasks);    //找到一个节点,就可以用这个节点的tasks字段,找到这个结构体的地址.对应的字段tasks
                //此时的p指针已经指向task_struct结构体的首部,后面就可以通过p指针进行操作
                count++;  //找到一个进程,自加
                printk("\n\n");
                printk("pid: %d; state: %d; flags:%d;ptrace:%d;prior: %d; static_pri: %d;normal_prio:%d; parent_pid: %d; count: %d; umask: %d",p->pid,p->state,p->flags,p->ptrace,p->prio,p->static_prio,p->normal_prio,(p->parent)->pid,atomic_read(&(p->files)->count),(p->fs)->umask); 
        }
 
        printk("进程的个数:%d\n",count);
 
        return 0;
}
 
static void __exit exit_pcb(void)    //出口函数
{
        printk("Exiting...\n");
}
 
// 指明入口点与出口点,入口/出口点是由module.h支持的
module_init(print_pcb);
module_exit(exit_pcb);

Makefile代码:

obj-m:= task_struct.o

CURRENT_PATH:=$(shell pwd)	#模块所在的当前所在路径
LINUX_KERNEL:=$(shell uname -r)	#linux内核代码的当前版本
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)	#linux内核的当前版本源码路径

all:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules	#编译模块
#				内核的路径		  当前目录编译完放哪   表明编译的是内核模块

clean:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean	#清理模块

运行结果:

在这里插入图片描述

(二)源码学习

struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
	/*
	 * For reasons of header soup (see current_thread_info()), this
	 * must be the first element of task_struct.
	 */
	struct thread_info		thread_info;	//thread_info用于保存需要频繁访问和快速访问的字段
#endif
	/* -1 unrunnable, 0 runnable, >0 stopped: */
	volatile long			state;		//state也就是它的状态信息

	/*
	 * This begins the randomizable portion of task_struct. Only
	 * scheduling-critical items should be added above here.
	 */
	randomized_struct_fields_start

	void				*stack;		//*stack是内核栈
	refcount_t			usage;
	/* Per task flags (PF_*), defined further below: */
	unsigned int			flags;		//flags是进程的标记
	unsigned int			ptrace;		//ptrace字段是用来实现断点调试的
	
......

#endif
	int				on_rq;

	int				prio;			//pio是动态优先级
	int				static_prio;	//static_prio是静态优先级
	int				normal_prio;	//normal_prio是正常优先级
	unsigned int			rt_priority;	//rt_priority是实时优先级
	
......

	unsigned int			policy;			//policy是进程调度策略

......

	struct sched_info		sched_info;		//调度器统计进程的运行信息sched_info,它是一个结构体

	struct list_head		tasks;		/*tasks字段,这是一个双向链表,正是字段把所有的进程连接到了一块,我们才能对进程进行遍历*/
	
......

	pid_t				pid;		//进程标识符
	pid_t				tgid;		//线程组id

......

	/* Real parent process: */
	struct task_struct __rcu	*real_parent;	//指向创建其的父进程,如果其父进程不存在,则指向init进程

	/* Recipient of SIGCHLD, wait4() reports: */
	struct task_struct __rcu	*parent;		//指向当前的父进程,通常与real_parent一致

	/*
	 * Children/sibling form the list of natural children:
	 */
	struct list_head		children;		//子进程链表
	struct list_head		sibling;		//兄弟进程链表
	struct task_struct		*group_leader;	//线程组领头线程指针

......

	unsigned long			nvcsw;		//nvcsw反映主动上下文切换的次数
	unsigned long			nivcsw;		//nivcsw反映被动上下文切换的次数

......

	struct fs_struct		*fs;		//当前目录

	/* Open file information: */
	struct files_struct		*files;		//指向文件描述符,该进程所有打开的文件会在这里面的一个指针数组里

......

	/* Signal handlers: */
	struct signal_struct		*signal;		/*信号描述符,用于跟踪共享挂起信号队列,被属于同一线程组的所有进程共享,也就是同一线程组的线程此指针指向同一个信号描述符*/
	struct sighand_struct		*sighand;	//信号处理函数描述符
	sigset_t			blocked;			//被阻塞信号掩码
	sigset_t			real_blocked;		//被阻塞信号的临时掩码
	/* Restored if set_restore_sigmask() was used: */
	sigset_t			saved_sigmask;		//私有挂起信号队列
	
	......
	
};

​ pid是进程标识符
​ tgid,因为linux内核支持内核即线程,每个进程中的所有线程就形成了一个线程组,线程组就需要一个领导,那么这个领导就是所在进程的pid。**一个进程就是一个线程组,所以每个进程的所有线程都有着相同的tgid。**当程序开始运行时,只有一个主线程,这个主线程的tgid就等于pid。而当其他线程被创建的时候,就继承了主线程的tgid。这样,内核就可以通过tgid知道某个task属于哪个线程组,也就知道属于哪个进程了。当我们用ps命令或者getpid()等接口查询进程id时,内核返回给我们的也正是这个tgid。

三、对进程控制块的认识

对进程控制块新的认识:

​ 1.进程控制块可以用内核中的一个结构体task_struct来描述

​ 2.系统是根据进程的PCB感知进程的存在的,PCB是进程存在和运行的唯一标志

​ 3.PCB是内核中被频繁读写的数据结构,故应常驻内存

自己的认识:

​ 进程控制块对一个进程来说是一个非常重要的一部分,如果把进程比作是一个人的话,那么进程控制块就是这个人的心脏,控制着人的各个器官。

四、打印特定进程的相关信息

方法一:采用向内核模块传递参数的方法

知识点补充:

module_param(name, type, perm)

功能:内核模块传参

参数:

@name 变量名/传参名

@type 参数的数据类型,short ,ushort(无符号短整型),int ,uint ,charp(字符指针)

@perm 权限,一般情况下,我们不需要在模块执行以后,进行参数传递,所以perm权限一般设置成0

对上述task_struct.c代码进行修改,代码如下:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>   		//task结构体
#include <linux/fdtable.h>		//files
#include <linux/init_task.h>
#include <linux/fs_struct.h>

MODULE_LICENSE("GPL");  //许可证

int my=0;
module_param(my, int, 0);

//入口函数
static int __init print_pcb(void)    //init宏是由init.h文件所支持
{
        struct task_struct *task,*p;
        struct list_head *pos;   //双向链表
 
        printk("begin...\n");
 
        //对链表遍历时,希望从第一个开始
        task=&init_task;  //指向0号进程pcb
 
        list_for_each(pos,&task->tasks) //遍历操作,使用pos指向,传入的参数task指向tasks字段.0号进程的tasks进行遍历。tasks将所有的进程连在一块。
        {
            p=list_entry(pos,struct task_struct,tasks);    //找到一个节点,就可以用这个节点的tasks字段,找到这个结构体的地址.对应的字段tasks
			if(p->pid==my)
			{
                printk("pid: %d; state: %d; flags:%d;ptrace:%d;prior: %d; static_pri: %d;normal_prio:%d; parent_pid: %d; count: %d; umask: %d\n",p->pid,p->state,p->flags,p->ptrace,p->prio,p->static_prio,p->normal_prio,(p->parent)->pid,atomic_read(&(p->files)->count),(p->fs)->umask); 
            }
        }
 
        return 0;
}
 
static void __exit exit_pcb(void)    //出口函数
{
        printk("Exiting...\n");
}
 
// 指明入口点与出口点,入口/出口点是由module.h支持的
module_init(print_pcb);
module_exit(exit_pcb);

依次执行如下指令:

make

//加载的时候,如果传递参数,则变量值就是传递过来的值,否则就是默认的初始化值

sudo insmod task_struct.ko my=1 //这里以pid为以1的进程为例

dmesg

方法二:直接在代码中指定打印的进程

对上述task_struct.c代码进行修改,代码如下:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>   		//task结构体
#include <linux/fdtable.h>		//files
#include <linux/init_task.h>
#include <linux/fs_struct.h>

MODULE_LICENSE("GPL");  //许可证

int my=?;				//用来指定打印进程的pid,注意:此处要把?换成pid

//入口函数
static int __init print_pcb(void)    //init宏是由init.h文件所支持
{
        struct task_struct *task,*p;
        struct list_head *pos;   //双向链表
 
        printk("begin...\n");
 
        //对链表遍历时,希望从第一个开始
        task=&init_task;  //指向0号进程pcb
 
        list_for_each(pos,&task->tasks) //遍历操作,使用pos指向,传入的参数task指向tasks字段.0号进程的tasks进行遍历。tasks将所有的进程连在一块。
        {
            p=list_entry(pos,struct task_struct,tasks);    //找到一个节点,就可以用这个节点的tasks字段,找到这个结构体的地址.对应的字段tasks
			if(p->pid==my)
			{
                printk("pid: %d; state: %d; flags:%d;ptrace:%d;prior: %d; static_pri: %d;normal_prio:%d; parent_pid: %d; count: %d; umask: %d\n",p->pid,p->state,p->flags,p->ptrace,p->prio,p->static_prio,p->normal_prio,(p->parent)->pid,atomic_read(&(p->files)->count),(p->fs)->umask); 
            }
        }
 
        return 0;
}
 
static void __exit exit_pcb(void)    //出口函数
{
        printk("Exiting...\n");
}
 
// 指明入口点与出口点,入口/出口点是由module.h支持的
module_init(print_pcb);
module_exit(exit_pcb);

**疑惑:**无论是采用上述哪种方法,最后只有在卸载模块后,才会对pid为1的进程信息进行输出

插入模块后dmesg:

在这里插入图片描述

卸载模块后dmesg:

在这里插入图片描述

**已解决:**未在printk语句中加入\n,加入后即可正常打印(上述代码已做修改),感觉输出的内容(在缓冲区)是通过\n(也就是回车)被写进日志文件的,在日志文件中每个输出的内容为一行进行记录。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值