Linux之进程

进程是什么?
  • 程序:程序是一个普通文件,是机器代码指令和数据的集合;也可以这么说,程序代表我们期望完成某工作的计划和步骤。
  • 进程:程序的执行过程可以说是一个执行环境的综合,这个执行环境除了包括程序中各种指令和数据外,还有一些额外的数据,比如寄存器的值、用来保存临时数据的堆栈、被打开的文件输入/输出的状态等。上述执行环境的动态变化表征了程序的运行,为了对这个动态变化的过程进行描述,程序这个概念已经远远不够,于是引入了进程这个概念。

  • 进程控制块(PCB):
    每个进程都有一个描述其状态、优先级、链接信息、各种标识符、进程间通信、时间和定时器信息、文件系统信息、虚拟内存信息等的task_struct结构体,传统上这样的数据结构叫做PCB(Process Control Block),

  • 其中进程的状态转换以及所调用的函数如下图所示:

image

进程控制块的存放
  • 当操作系统创建一个进程时,内核首先要给其分配一个PCB(task_struct),那么这个PCB存放在哪儿?怎么找到呢?如下图所示:
    image

  • 每当进程从用户态进入内核后都要使用栈,这个栈叫做进程的内核栈,当进程已进入内核态,CPU就自动设置该进程的内核栈,这个栈在内核的数据段上。为了节省空间,linux把内核栈和一个紧挨近PCB的小数据结构thread_info放在一起,占用8K的空间,如上图所示。在Intel系统中,栈起始于末端,并朝着这个内存区开始的方向增长。用户态切换到内核态时,进程的内核栈总是空的,因此堆栈寄存器esp直接指向这个内存区的顶端。从用户态到内核态后只要版数据写到栈中,堆栈寄存器的值就朝箭头方向递减,而task是thread_info的第一个数据项,所以找到thread_info就能很容易的找到当前运行的task_struct了。把thread_info和内核栈放在一起的好处就是内核很容易从esp寄存器的值获得当前在cpu上正在运行的thread_info结构的地址,事实上如果thread_info结构长度是8K,则内核屏蔽esp的低13位就可以获得thread_info结构的基地址,可以通过current_thread_info()来完成,它产生如下汇编代码:

movl $ 0xffffe000, % ecx
andl % esp, % ecx
movl % ecx, p

这三条指令执行完成以后,p就是指向进程的thread_info的结构的指针,为了获得当前在CPU上的运行的PCB指针,可以调用current宏,本质上等价于current_thread_info()->task。可以把current当做全局变量来使用,current->pid返回当前正在执行的进程的标识符,获得其父进程的PCB可以通过struct task_struct *my_parent=current->parent

下面的内核代码为打印进程的pid和进程名

#include <linux/module.h>                                                       
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/list.h>

static int __init getpid_init(void)
{
    struct task_struct *task,*p;
    struct list_head *pos;
    int count = 0;
    printk("<1>hello,world! from the kernel space......\n");
    task = &init_task;
    list_for_each(pos,&task->tasks)
    {
        p = list_entry(pos,struct task_struct,tasks);
        count++;
        printk("<1>%d--->%s\n", p->pid, p->comm);
    }
    printk("<1>the num of process is:%d\n", count);
    return 0;
}

static void __exit getpid_cleanup(void)
{
    printk("<1>goodbye,world! leaving kernel space...\n"); 
}

module_init(getpid_init);
module_exit(getpid_cleanup);
MODULE_LICENSE("GPL"); 


其中Makefile的内容为:
obj-m := getpid.o
module-objs := getpid.o
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)

all:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

结果如下列所示:

[ 2734.430992] <1>1--->init
[ 2734.430993] <1>2--->kthreadd
[ 2734.430994] <1>3--->ksoftirqd/0
[ 2734.430995] <1>4--->kworker/0:0
[ 2734.430996] <1>5--->kworker/0:0H
[ 2734.430997] <1>7--->rcu_sched
[ 2734.430998] <1>8--->rcu_bh
[ 2734.430999] <1>9--->migration/0
[ 2734.431000] <1>10--->watchdog/0
[ 2734.431001] <1>11--->kdevtmpfs
[ 2734.431002] <1>12--->netns
[ 2734.431003] <1>13--->perf
......
......
[ 2734.431276] <1>2643--->bash
[ 2734.431277] <1>2676--->update-notifier
[ 2734.431278] <1>2722--->deja-dup-monito
[ 2734.431279] <1>3840--->dhclient
[ 2734.431280] <1>4231--->kworker/u256:2
[ 2734.431281] <1>4887--->vim
[ 2734.431282] <1>5527--->sudo
[ 2734.431283] <1>5528--->insmod
进程的一生
  • 首先调用fork(或者vfork,clone),一个新的进程呱呱坠地,但是这还只是父进程的一个克隆(写时复制)。随后调用exec簇函数,新的进程有了独立的空间
  • 人有生老病死,进程也一样,它可以是自然死亡,即运行到main函数的最后一个“}”;也可能是中途退场,退场有两种方式,一种是调用exit函数,另外一种是main函数内使用return,无论哪一种都可以留下遗言,放在返回值中保留下来;甚至可能被其他进程通过其他方式结束生命
  • 进程死掉以后会留下一个空壳,wait站好最后一班岗,负责收集PCB,这就是进程完整的一生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值