一、通过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 | 优先级 |
NI | nice值 负值表示高优先级,正值表示低优先级 |
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(也就是回车)被写进日志文件的,在日志文件中每个输出的内容为一行进行记录。