跟我一起学Linux系统编程006A-进程、内存布局

Linux系统编程006A-进程、内存布局

1 进程和程序

可执行的文件,躺在硬盘上的叫程序,运行起来了就叫进程。

从内核 的层面来看,进程由用户内存空间和一系列内核数据结构组成。其中,用户内存空间包含了程序代码和代码使用的变量,内核数据结构用于维护进程的状态信息。这些记录在内核数据结构的信息有:进程标识号IDs、虚拟内存表、打开文件描述符表、信号传递及处理的相关信息、进程资源使用和限制、当前工作目录、环境变量、命令行等等大量的相关信息。

2 进程号和父进程号

定义在”<stdlib.h>“中的系统调用函数getpid()、getppid()。下面是一个简单的测试。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
 
int main(int argc ,char *argv[])
{      
		int sleep_seconds=0;
	long current_pid = (long) getpid();
	long parent_pid = (long) getppid();

	printf("程序:%s,PID是:%ld,PPID是:%ld\n",argv[0], current_pid,parent_pid );

	if (  argc >= 2 )
	{
		sleep_seconds = atoi(argv[1]);
	}        

	if ( sleep_seconds > 0)
	{
		printf("程序:%s,PID是:%ld,正在睡眠,需等%d秒钟\n",argv[0], current_pid,sleep_seconds );
		sleep(sleep_seconds);
	}
	printf("程序:%s,PID是:%ld,PPID是:%ld,运行结束\n",argv[0], current_pid,parent_pid );
	return 0;
}

gcc main.c 编程后生成a.out

./a.out 10 &

[1] 9320

程序:./a.out,PID是:9320,PPID是:8518

程序:./a.out,PID是:9320,正在睡眠,需等10秒钟

pidof a.out

9320

程序:./a.out,PID是:9320,PPID是:8518,运行结束

[1]+ 已完成 ./a.out 10

你可能会想到,在系统中能运行无穷尽的进程吗?答案当然是否定的,PC功能再强大,内存、硬盘和CPU等算力资源也是有限的。内核 常量ID_MAX定义,32位Linux的进程号上限为32767。(因为:PID_MAX=0x8000,因此进程号的最大值为0x7fff,即32767)。在64位Linux平台上是4194304。具体可用下面的两种命令得到这个值(我的是64位Ubuntu20):

$ cat /proc/sys/kernel/pid_max 
   4194304    
$ sudo sysctl -a | grep pid_max
   kernel.pid_max = 4194304    

注意,我们创建的进程号一般会大于300,这是因为进程号0-299保留给daemon进程。

由于一般机器不可能同时跑那么多进程+线程,所以32768(32位)/4194304(64位)是肯定够用了,但是系统倾向于分配未使用过的pid给新进程,所以你会发现在正在运行的系统上,有很多低位的pid没有使用,那是因为启动的时候该pid被其它程序用过了,当然,你真有本事用到pid的最大值,系统也有办法解决,那就是从头(低位)搜索未被占用的pid分配给新进程。

除了极少数系统进程外(比如init的进程号是1),一般情况下,程序与进程号没有固定关系。

如果一个父进程终止了,那么子进程将成为”孤儿“,init进程将接管这个子进程。但是在Ubuntu20上,我用终端测试的结果是:父进程终止,子进程也会被强行终止,测试步骤如下:

(1)打开A终端:

$ echo $$

16830 A终端的进程号是16830

$ kolourpaint & 启动一个名为kolourpaint的画图进程

(2)打开B终端:

$ pidof kolourpaint

523663 (kolourpaint的进程号)

$ ps -ef | grep kolourpaint 用这个命令再证实A终端进程与kolourpaint进程是父子关系

songguo+ 523663 16830 0 21:21 pts/0 00:00:00 kolourpaint

(3)关闭A终端,相当于是关闭了父进程

(4)再到B终端中输入

$ pidof kolourpaint 找不到kolourpaint

$ ps -ef | grep kolourpaint 找不到kolourpaint

3 进程内存布局

在32位系统上,进程一旦启动,就会申请4G虚拟地址空间.在64位系统上,会申请256TB虚拟地址空间。进程的空间有以下内存分段:

栈:局部变量、const局部常量、函数参数、返回地址等

堆:动态分配的内存,

BSS段:可读+可写,未初始化/初始化为0的静态变量/全局变量

数据段:可读+可写,初始化为~0的静态变量/全局变量

代码段:只读+可执行,可执行代码、常量(字符串常量;const全局常量;enum常量;#define常量等)

Linux系统编程006A-进程、内存布局

Linux系统编程006A-进程、内存布局

上图对于每个进程的内存布局都是一样的。但地址是虚拟的,从整个系统来看,地址是不相冲突的。比如,每个应用程序都是从0x80480000这个地址开始的,这个地址是一个虚拟地址,它映射到物理内存的不同内存段,所以不会冲突。

4 Linux虚拟地址空间布局

Linux运行在虚拟地址空间,并负责把系统中实际存在的远小于4GB的物理地址空间(物理内存)根据不同需求,映射到整个4GB的虚拟地址空间中。这也就说:同一块物理内存可能映射到多处虚拟内存地址空间上,这正是Linux内存管理职责所在。

虚拟地址通过页表(Page Table)映射到物理内存,页表由操作系统维护并被处理器引用。内核空间在页表中拥有较高特权级,因此用户态程序试图访问这些页时会导致一个页错误(page fault)。

在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存。内核代码和数据总是可寻址,随时准备处理中断和系统调用。与此相反,用户模式地址空间的映射随进程切换的发生而不断变化。

Linux进程在虚拟内存中的标准内存段布局如下图所示:

Linux系统编程006A-进程、内存布局

上图中,用户地址空间中的蓝色条带对应于映射到物理内存的不同内存段,灰白区域表示未映射的部分。这些段只是简单的内存地址范围,与CPU处理器的段没有关系。

上图中Random stack offset和Random mmap offset等随机值意在防止恶意程序。Linux通过对栈、内存映射段、堆的起始地址加上随机偏移量来打乱布局,以免恶意程序通过计算访问栈、库函数等地址。execve(2)负责为进程代码段和数据段建立映射,真正将代码段和数据段的内容读入内存是由系统的缺页异常处理程序按需完成的。另外,execve(2)还会将BSS段清零。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虎哥的世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值