0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux应用开发之进程和程序

嵌入式应用研究院 来源:嵌入式应用研究院 2023-06-04 16:35 次阅读

1.进程和程序

进程是一个可执行程序的实例,程序包含了一系列信息文件,这些信息描述了如何在运行期间创建一个进程:

二进制格式标识 :包含用于描述可执行文件格式的元信息,内核根据此信息来解释文件中的其他信息:

a.out(汇编程序输出)

COFF(通用文件格式)

ELF(可执行连接格式),目前大多数 linux 实现采用此种方式,具有更多优点

机器语言指令 :对程序算法进行编码

程序入口地址 :表示程序开始执行时的起始指令位置

数据 :程序文件包含的变量初始值和程序使用的字面量值(比如字符串)

符号表及重定位表 :描述程序中函数和变量的位置及名称,包括调试和运行时的符号解析(动态链接)

共享库和动态链接信息 :程序文件所包含的一些字段,列出程序运行时需要使用的共享库,以及加载共享库的动态链接器的路径名

其他信息 :程序文件还包含了很多其他信息,用以描述如何创建进程

可以用一个程序来创建很多进程。

从内核角度来看:

进程是内核定义的抽象的实体,并为该实体分配用以执行程序的各项系统资源。

进程由用户内存空间和一系列内核数据结构组成,用户内存空间包含了程序代码及代码所使用的变量,内核数据结构则用于维护进程状态信息,其中包括:

进程相关的标识号

虚拟内存表

打开文件的描述表

信号传递和处理的相关信息

进程资源使用及限制

当前工作目录

一些其他信息

2.进程号和父进程号

每个进程都有一个进程号 PID,是一个正数,唯一标识系统中的某个进程。

#include 
#include 

pid_t getpid(void);

getpid 返回值的数据类型是 pid_t ,专用于存储进程号

Linux 内核限制进程号需要小于 32767(内核常量 PID_MAX 定义),新进程创建时,内核会按顺序将一个可用的进程号分配给其使用,进程号到达 32767 的限制时,内核将重置进程号计数器为300(不是1)

#include 
#include 

pid_t getppid(void);

getppid 获取父进程的进程号

看 /proc/PID/status 文件提供的 PPid 字段可以获知每个进程的父进程

pstree 命令可以查看进程家族树

3.进程内存布局

每个进程所分配的内存都由很多虚拟内存逻辑划分的部分,称之为 "段":

文本段 :包含了进程运行的程序机器语言指令,文本段是只读的,也是可共享的

初始化数据段 :包含显式初始化的全局变量和静态变量

未初始化数据段 :包含了未显示初始化的全局变量和静态变量,程序启动之前,系统将本段内所有内存初始化为0,这个段又称为 BSS(block started by symbol) 段。未初始化的全局变量和静态变量与初始化的全局变量和静态变量分开放的原因:

程序在磁盘存储时,没有必要为未经初始化的变量分配存储空间

可执行文件只需要记录未初始化数据段的位置及大小,直到运行时再有程序加载器来分配这一空间

栈:动态增长和收缩的段,由栈帧组成,系统会为每个当前调用的函数分配一个栈帧,栈帧中存储了函数的局部变量,实参和返回值

堆 :在运行时为变量动态进行分配的一块区域,堆顶端称为 program break

初始化数据段和未初始化数据段又常被称为用户初始化数据段和零初始化数据段。

size 命令可以显示二进制文件的文本段,初始化数据段,未初始化数据段的大小。

10c07d58-025d-11ee-90ce-dac502259ad0.png

argv 和 environ 用来存储程序的命令行实参和环境列表

十六进制的地址会因为内核配置和程序链接选项的差异而有所不同

灰色区域表示这些范围在进程虚拟地址空间中不可用,也就是说没有为这些区域创建页表

4.虚拟内存管理

linux 内核采用虚拟内存管理技术,该技术利用了大多数程序的一个典型特征,即访问局部性:要求高效使用 CPU 和 RAM(物理内存)资源:

空间局部性 :程序倾向于访问在最近访问过的内存地址附近的内存(指令是顺序执行的)

时间局部性 :程序倾向于在不久将来再次访问最近刚访问的内存地址(由于循环)

正是因为局部性特征,使得程序即便在仅有部分地址空间存在于 RAM 中,依然可以获得执行。

虚拟内存的规划之一就是将每个程序使用的内存切割成小型的,固定大小的 "页" 单元。相应的,将 RAM 划分成一系列与虚拟页尺寸相同的页帧:

任意时刻,每个程序仅有部分页驻留在物理内存页帧中,这些页构成了所谓的驻留集

程序未使用的页拷贝保存在交换区,交换区是磁盘空间中的保留区域,是作为计算机 RAM 补充,仅在需要时才会载入物理内存

若进程欲访问的页面并未驻留在物理内存中,将会发生页面错误(page fault),内核即刻挂起进程的执行,同时从磁盘中将该页面载入内存

页面的大小通常为 4096 个字节,但也有的实现使用更大的页面。

内核中维护了一张页表,该页表描述了每页在进程虚拟地址空间中的位置:页表中的每个条目要么指出这个虚拟页面在 RAM 中的具体位置,要么表明其驻留在磁盘上。

进程虚拟地址空间中,并非所有的地址范围都需要页表条目:

由于可能存在大段的虚拟地址空间并未投入使用,因此也不需要为其维护页表条目

进程试图访问的地址无页表条目与之对应,那么进程将收到 SIGSEGV 信号

由于内核能够为进程分配和释放页和页表条目,所以进程的有效地址范围在其声明周期内可以发生变化:

栈向下增长超出了之前未达到的位置

堆中分配或者释放内存时引起 program break 位置的变化

调用 shmat() 连接 System V 共享内存区或者调用 shmdt() 脱离共享内存区时

调用 mmap() 创建内存映射时,或者调用 munmap() 解除内存映射时

10e62206-025d-11ee-90ce-dac502259ad0.png

虚拟内存的实现需要硬件中分页内存管理单元(PMMU)的支持,PMMU 把要访问的每个虚拟地址转换成相应的物理内存地址,当特定虚拟内存地址所对应的页没有驻留在 RAM 中时,将以页面错误通知内核。

虚拟内存管理使虚拟地址空间与 RAM 物理地址隔离,带来的优点:

进程与进程,进程与内核相互隔离,一个进程不能读写另一个进程或内核中的数据,因为每个进程的页表条目指向 RAM 或交换分区中不同的物理页面集合

适当情况下,两个或者更多的进程能够共享内存,因为内核可以使不同进程的页表条目指向相同的 RAM 页,经常发生在下面的情形:

多个程序执行相同的程序文件或者加载相同的共享库时,会共享一份代码副本

可以使用 shmget() 和 mmap() 等系统调用显式的请求与其它进程共享内存区,这么做是出于通信的目的

便于实现内存保护机制,可以对页表条目进行标记,以表示相关页面内容是可读、可写、可执行,多个进程共享 RAM 页面时,允许每个进程对内存采取不同的保护措施,例如一个进程可能以只读方式访问某页面,而另一进程则以读写方式访问同一页面

程序员和编译器、链接器之类的工具无需关注在 RAM 中的物理布局

因为需要驻留在内存中的仅是程序的一部分,所以程序在加载和运行时都很快,而且,一个进程所占用的虚拟内存大小能够超出 RAM 容量

每个进程使用的 RAM 减少了,RAM 中同时可以容纳的进程数量就增多了,从而任意时刻,CPU 都可执行至少一个进程,CPU 的利用率也会提高

5.栈和栈帧

11023fea-025d-11ee-90ce-dac502259ad0.png

栈驻留在内存的高端,并向下增长,专用寄存器——栈指针(stack pointer),用于跟踪当前栈顶。

每次函数调用时,会在栈上新分配一帧,每当函数返回,再从栈上将此帧移去。

通常将这里的栈称为用户栈,以便与内核栈加以区分,内核栈是每个进程驻留在内核内存中的内存区域,在执行系统调用的过程中供内核内部函数调用使用。

每个用户栈包括的信息:

函数实参和局部变量

函数调用链接信息

6.命令行参数

111a1d86-025d-11ee-90ce-dac502259ad0.png

argc 表示命令行参数的个数

argv 是指向命令行参数的指针数组,每一个参数都是以空字符 null 结尾的字符串,第一个参数,即 argv[0] 通常是程序名,argv[argc] 是 NULL

要想从程序内的任一位置访问部分或者全部内容,还有两种方法:

/proc/PID/cmdline 文件可以读取任一进程的命令行参数,每个参数都以 null 字节终止,程序可以通过 /proc/self/cmdline 文件访问自己的命令行参数

GNC C 语言库提供两个全局变量,可以在程序中内获取调用该程序时的程序名称,即命令行的第一个参数,定义 _GNU_SOURCE 后,从 中获取:

program_invocation_name :提供了用于调用该程序的完整路径名

program_invocation_short_name :提供了程序基本名称

argv 驻留于进程栈之上的一个内存区域,此区域可存储的字节数有上限要求, 中的 ARG_MAX 常量规定了其大小,一般要求下限是 4096 个字节,调用 sysconf(_SC_ARB_MAX) 确定此值。

7.环境列表

每个进程都有一个与其相关的称之为环境列表的字符串数组,或者简称为环境:

环境是 "名称-值" 的成对集合

常将列表中的名称称为环境变量

新进程在创建时会继承其父进程的环境副本,这种传递是单向的,一次性的,子进程创建后,父、子进程均可各自修改自己的环境变量,且这些变更对对方是不可见的

环境变量常用于 shell 中,通过在自身环境变量中存放变量值,shell 可以确保将这些值传递给其所创建的进程

可以通过设置环境变量来改变一些库函数的行为

大多数 shell 使用 export 添加环境变量:

export SHELL=/bin/bash

printenv 命令可以显示当前的环境列表,export 命令不加任何参数时也可以达到此目的。

通过 /proc/PID/environ 文件可以检查任一进程的环境列表。

8.从程序中访问环境

C 语言使用全局变量 char** environ 访问列表。

1133efa4-025d-11ee-90ce-dac502259ad0.png

#include 

char *getenv(const char *name);

根据换了变量的名称,返回相应的字符串指针,如果该环境变量不存在则返回 NULL

9.修改环境

#include 

int putenv(char *string);

向调用进程的环境添加一个新的变量或者修改一个已经存在的变量值

string 是指向 name=value 形式的字符串

调用函数后,该字符串就称为环境变量的一部分,environ 变量的某一元素的指向与 string 参数指向相同,而非 string 参数指向字符串的副本,因此:

后续修改 string 将会影响进程的环境

string 参数不能是栈空间变量

调用失败返回非 0 值,而不是 -1

glibc 中将 putenv 做了非标准扩展,如果 string 中不包含 =,环境列表将会移除以 string 命名的环境变量

#include 

int setenv(const char *name, const char *value, int overwrite);

会为 name=value 的字符串分配内存缓冲区,并将 name 和 value 所指的字符串复制到缓冲区,创建一个新的环境变量

name 结尾处 和value开始处不要加 =,因为 setenv() 会自动添加

name 的环境变量在环境中已经存在,且参数 overwrite 的值是0,则 setenv() 不改变环境,如果 overwrite 的值非0,则 setenv() 将总是改变环境

之后修改 name 和 value 以及使用栈上变量都不会有影响

#include 

int unsetenv(const char *name);

将 name 指定的环境变量从环境列表中移除

#include 

int clearenv(void);

清除环境变量,即设置 environ=NULL

需要预定义 _SVID_SOURCE 或者_BSD_SOURCE

使用 setenv 和 clearenv() 可能导致内存泄漏:

调用 setenv 分配一块内存缓冲区,随即称为进程环境的一部分,调用 clearenv 并不会释放上述的缓冲区

但是一般这不会称为一个问题,因为程序一般在开始处调用 clearenv() 以清除继承自父进程的环境

10.执行非局部跳转

#include 

int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);

setjmp() 和 longjmp() 可执行非局部跳转,"非局部" 指的是跳转目标为当前执行函数之外的某个位置

setjmp() 调用为后续 longjmp() 调用执行的跳转确立了跳转目标,该目标就是程序发起 setjmp() 调用的位置

通过查看 setjmp() 返回的整数值,可以区分 setjmp() 是初始返回还是第二次返回:

初始调用返回 0

后续 "伪返回" 的返回值为 longjmp() 调用中 val 参数指定的任意值,如果 val 指定为 0,则 longjmp() 实际返回时用 1 替换

setjmp() 将环境变量 env 参数中,调用 longjmp() 时必须指定相同的 env 变量,因为跳转发生在不同的函数,所以:

env 定义为全局变量

env 作为函数的参数传递,此种方法比较少见

调用 setjmp() 时,env 除了保存当前进程的其他信息外,还保存了程序计数器(指向当前正在执行的机器语言指令) 和栈指针寄存器(标记栈顶)的副本,这些信息保证后续 longjmp() 调用完成后执行两个关键的操作:

将发起 longjmp() 调用的函数与之前调用 setjmp() 的函数之间的函数栈从栈上剥离,称为栈解开,这是通过将栈指针寄存器重置为 env 参数的保存值

重置程序计数寄存器,使得程序得以从初始的 setjmp() 调用位置继续执行,这是通过 env 参数中的程序计数寄存器实现的

编译器优化会重组程序的指令执行顺序,并在 CPU 寄存器中而非 RAM 中存储某些变量,这些优化不会将 setjmp() 和 longjmp() 考虑在内,因而当有优化时,可能出错。

程序中应该尽可能避免使用非局部跳转。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10981

    浏览量

    206673
  • 计数器
    +关注

    关注

    32

    文章

    2120

    浏览量

    92888
  • PID控制
    +关注

    关注

    10

    文章

    440

    浏览量

    39691
  • LINUX内核
    +关注

    关注

    1

    文章

    311

    浏览量

    21386
  • 机器语言
    +关注

    关注

    0

    文章

    35

    浏览量

    10697

原文标题:Linux应用开发之进程和程序

文章出处:【微信号:嵌入式应用研究院,微信公众号:嵌入式应用研究院】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux驱动开发_视频广告机开发Linux进程编程介绍

    介绍Linux进程编程、进程的创建、进程通信、完成广告机项目代码。
    的头像 发表于 09-17 15:49 932次阅读
    <b class='flag-5'>Linux</b>驱动<b class='flag-5'>开发</b>_视频广告机<b class='flag-5'>开发</b>、<b class='flag-5'>Linux</b><b class='flag-5'>进程</b>编程介绍

    Linux开发_Linux进程编程

    介绍Linux进程概念、进程信号捕获、进程管理相关的命令的使用等知识点。
    的头像 发表于 09-17 15:38 1093次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>开发</b>_<b class='flag-5'>Linux</b>下<b class='flag-5'>进程</b>编程

    Linux进程间如何实现共享内存通信

    这次我们来讲一下Linux进程通信中重要的通信方式:共享内存作为Linux软件开发攻城狮,进程间通信是必须熟练掌握的重要技能,而共享内存是在
    发表于 04-26 17:14 571次阅读

    Linux下的进程结构

    `#嵌入式培训#华清远见嵌入式linux学习资料《Linux下的进程结构》,进程不但包括程序的指令和数据,而且包括
    发表于 08-05 11:05

    Linux进程间通信方式-管道

    应用程序开发》热点链接:1、Linux进程间通信2、实验:编写守护进程3、实验:编写多进程
    发表于 08-29 15:29

    Linux学习杂谈】进程初步

    Linux底下我们使用gcc来完成这个工作,那么这个引导代码是什么意思呢?也就是说这个程序的运行它必须具备一定的条件,在这个系统环境下它要就有自己的生存环境,能够在这个环境中运行的条件。2.第二个就是程序
    发表于 08-21 17:00

    Linux学习杂谈】进程的诞生和消失

    死机。 来看下几个进程的概念:僵尸进程:(1)子进程先于父进程结束Linux系统设计的时候,当每一个进程
    发表于 09-01 20:38

    Linux学习杂谈】进程通信

    本帖最后由 michael_llh 于 2016-10-17 13:14 编辑 我们在Linux应用编程当中如果需要用到多个进程来完成一个任务的话那么我们就没有办法避开进程间通信的问题,并且
    发表于 10-15 14:45

    Linux进程管理

    Linux进程管理
    发表于 05-20 10:53

    Linux进程管理

    Linux进程管理 本章主要介绍进程的概念、状态、构成以及Linux进程的相关知识。 掌握进程
    发表于 04-28 14:57 0次下载

    嵌入式Linux应用程序开发详解

    。接着系统地讲解了嵌入式linux的环境搭建,以及嵌入式linux的i/o与文件系统的开发进程控制开发
    发表于 06-17 17:24 60次下载

    基于Linux进程管理的详细剖析

    上一篇,我们讲到了Linux内核开发和应用程序开发,今天我们来讲讲Linux重点部分Linux
    的头像 发表于 01-26 11:24 3473次阅读
    基于<b class='flag-5'>Linux</b><b class='flag-5'>进程</b>管理的详细剖析

    linux进程怎么查看

    Linux系统中,每个程序启动后可以创建一个或多个进程。例如,提供Web服务的httpd程序,当有大量用户同时访问Web页面时,httpd程序
    发表于 05-22 08:56 756次阅读
    <b class='flag-5'>linux</b>的<b class='flag-5'>进程</b>怎么查看

    Linux进程间通信方式——管道

    管道是Linux进程间通信的一种方式,它把一个程序的输出直接连接到另一个程序的输入。Linux的管道主要包括两种:无名管道和有名管道。
    发表于 06-01 09:13 1149次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>进程</b>间通信方式——管道

    你们知道Linux进程是怎样创建的吗

    一颗树的结构。就像下面这样:     在Linux中,为了创建一个子进程,父进程用系统调用fork来创建子进程。fork()其实就是把父进程
    的头像 发表于 11-09 10:46 2973次阅读
    你们知道<b class='flag-5'>Linux</b>的<b class='flag-5'>进程</b>是怎样创建的吗