当前位置: 首页 > article >正文

【北京迅为】iTOP-4412全能版使用手册-第二十四章 进程创建与回收

iTOP-4412全能版采用四核Cortex-A9,主频为1.4GHz-1.6GHz,配备S5M8767 电源管理,集成USB HUB,选用高品质板对板连接器稳定可靠,大厂生产,做工精良。接口一应俱全,开发更简单,搭载全网通4G、支持WIFI、蓝牙、陀螺仪、CAN总线、RS485总线、500万摄像头等模块,稳定运行Android 4.0.3/Android 4.4操作,系统通用Linux-3.0.15+Qt操作系统(QT支持5.7版本),Ubuntu版本:12.04,接口智能分配 方便好用。


第二十四章 进程创建与回收

24.1 本章导读

在 linux 中“文件”应该是最重要的抽象概念,从前面给大家准备的例程就可以看出来, 非常的多。仅次于“文件”的概念就是 linux 进程,本章介绍进程的一些基础概念以及简单的小例程。

本章配套视频为:

“视频 16_01linux 进程之进程基本概念”

“视频 16_02linux 进程之进程 ID”

“视频 16_03linux 进程之 exec 函数族”

“视频 16_04linux 进程之 fork 创建新进程”

“视频 16_05linux 进程之综合例程”

相信大多数人学习嵌入式都是从单片机开始学起的,有些单片机教程不严谨,给大家灌输一些偏颇的概念“单片机和 arm 一样”“学习了单片机编程,操作系统也是一样的”等等。

其实完全不一样!

中间隔了一层操作系统,也就是一层软件,嵌入式 Linux 系统很少需要开发者直接对寄存器操作,即使对寄存器的操作,也是移植,不会让你配置很多的寄存器。这部分可以理解为驱动移植也就是类似对硬件的操作,但是思路完全不一样了。

单片机上面的编程,一般被称为‘裸机程序’,也就是说我们在上面写个程序直接跑到硬件上面,并没有驱动程序和上层应用的概念,说直白些就是胡子眉毛一把抓,所有的程序都在自己写的代码里;这样会给我们有种掌控感,包括对寄存器的操作,中断函数都要自己写出 来,心里也会清晰明了。

但是,我们在做大型应用的时候,会用到很多技术,比如网络协议栈,图形系统等,这个时候再用‘裸机’就显得力不从心了,我们就需要在 Linux 架构的基础上来完成。

Linux 是个庞大的软件,很少人能把所有的代码看一遍,而且这样做也没有必要。这个时候就必须把一些概念‘抽象’出来,也就是说,在抽象的层次上去编程。我们需要学习 Linux 的架构和规范,在 Linux 框架的基础上进行编程。这时,很多单片机工作时养成的编程习惯就不适用了,很多人觉得会没有‘底气’,失去了裸机程序的掌控感,从而影响入门。

所以,我们必须要适应这种编程心态的改变,学会在操作系统基础上编程和做产品的思路与习惯。

几个基本概念

什么是‘程序’呢?科学的定义就是编译过的,可执行的二进制代码,这个很好理解。如果程序很大,可以叫做应用,这里提到的‘程序’以及‘应用’都是类似的概念。

‘进程’是指正在运行的程序,一个程序中可以包含多个进程;而一个进程可能包含一个或者多个‘线程’。

如下图所示,执行一下命令“top”。

接着看一下 x86-Ubuntu 系统的,下面的 pid 会不时的刷新变化。 

 

如下图所示,是开发板最小系统的进程。 

 

24.2 实验-进程ID

进程 id 基本概念

每一个进程都有一个唯一的标识符,进程 ID 简称 pid。

进程的 ID 在一个固定的时刻是唯一的,需要注意的是,假如你在 s 秒的时候有一个进程ID 是 1000,在 另外一个时刻 s+n,另一个进程 ID 也有可能是 1000。

另外内核运行的第一个进程是 1,也就是内核的 init 程序,这个是唯一的。

进程 id 一般默认的最大值为 32768,不过也是可以修改的,当然一般情况下不需要这么做。如果当前进程是 1000,那么下一个分配的进程就是 1001,它是严格线性分配的。直到pid 到了最大值,才重新分配已经用过的进程 ID,当然这些进程都是已经死亡的进程。

除了 init 进程,其它进程都是由其它进程创立的。创立新进程的进程叫父进程,新进程叫子进程。

使用‘man’命令学习 getpid 和 getppid

如下图所示,使用命令“man 2 getpid”。

 

如下图所示,有两个类似的函数 getpid 和 getppid。 

 

接着注意一下相关的函数,如下图所示,下面红框中的函数部分会在教程中介绍。 

接着介绍一下 getpid 和 getppid 的用法。

pid_t getpid(void);

参数:无。

返回值:成功返回进程号。

pid_t getppid(void);

参数:无。

返回值:成功返回父进程。

例程

编写简单的 getpid.c 文件测试 getpid 和 getppid 函数。首先添加头文件,如下图所示。

 

然后 main 函数如下图所示 

 

如上图代码所示。

第 10-12 行:获取子进程,没有则为 0。

第 13-16 行:获取父进程,前面提到过除了内核中 init 进程,其它进程都有父进程。

编译运行测试

在 Ubuntu 系统下,如下图所示,进入前面实验创建的目录“/home/linuxsystemcode”,使用命令“mkdir pid”新建 pid 文件夹,将源码 getpid.c 拷贝进去,进入新建的文件夹 pid,如下图所示。

使用命令“arm-none-linux-gnueabi-gcc -o getpid getpid.c -static”编译 getpid 文件,如下图所示,使用命令“ls”可以看到生成了 getpid 可执行文件。

这里介绍通过 U 盘拷贝来运行代码的方法,也可以编译进文件系统或者使用 NFS 文件系统等等。

将编译成的可执行文件 getpid,拷贝到 U 盘,启动开发板,插入 U 盘,加载 U 盘,运行程序如下。

 

如上图所示,pid927 是当前程序的进程号,pid1209 是当前进程的父进程。

查看当前程序的进程

Linux 中有进程配套的命令 ps 个 kill,可以查看和终止进程。如下图所示,ps 命令查看进程号。

如果是一个循环程序,执行到 while(1),运行的时候可以在执行命令后面添加&,后台运行,然后使用 ps 查询 id 号,使用 kill 命令结束进程。

24.3 实验-exec函数族

在学习创建进程之前,先来学习一下 linux 中重要的 exec 函数族。在 linux 中,exec 函数族是把程序直接载入内存,而不是在一个程序中运行多个进程。

 

如上图所示,最简单直白的解释就是 exec 函数族调用成功之后,会在内存中执行一个新的程序。在 linux 中要运行多任务需要使用 exec 函数族和 fork 进程(下一小节的内容)。

使用‘man 命令’学习 exec 函数

如下图所示,使用命令“man 3 exec”。

 

如下图所示,有六个类似的函数 execl, execlp, execle, execv, execvp, execvpe 。 

 

接着注意一下相关的函数,如下图所示。其中 fork 进程调用函数也是非常重要的,将在下一小节介绍。 

 

exec 函数族比较多,但是却很容易记忆和区分的,下面给大家总结一下它们的区别。 

 

如上图所示,首先函数族都是以 exec+xx 的方式命名的。

“l”和“v”表示参数是以列表还是以数组的方式提供的。

“p”表示这个函数的第一个参数是*path,就是以绝对路径来提供程序的路径,也可以以当前目录作为目标。

“e”表示为程序提供新的环境变量。

exec 函数族例程

编写简单的 exec.c 文件测试 exec 函数。首先添加头文件,如下图所示。

 

然后 main 函数如下图所示。 

如上图代码所示。 

第 1-4 行:头文件

第 8-12 行:使用 execl 函数执行“/mnt/udisk/”目录下的 helloexec 程序,传递参数helloexec 和 execl。

第 13-15 行:运行成功则不会执行,不正常则打印 execl error!。

接着编写简单的 helloexec.c 文件,如下图所示。

 

如上图所示,main 函数的第二个参数传递进入之后,argv[1]将被打印出来,如果正常执行则会打印“Hello execl!”。

编译运行测试

在 Ubuntu 系统下,如下图所示,进入前面实验创建的目录“/home/linuxsystemcode”,使用命令“mkdir exe”新建 exe 文件夹,将源码 execl.c 和helloexec.c 拷贝进去,进入新建的文件夹 exe,如下图所示。

 

使用命令“arm-none-linux-gnueabi-gcc -o execl execl.c -static”和“arm-none- linux-gnueabi-gcc -o helloexec helloexec.c -static”编译 execl 和 helloexec 文件,如下图所示,使用命令“ls”可以看到生成了 helloexec 和 execl 可执行文件。 

 

这里介绍通过 U 盘拷贝来运行代码的方法(可参考 10.3.5 小节),也可以编译进文件系统或者使用 NFS 文件系统等等

将编译成的可执行文件 execl 和 helloexec,拷贝到 U 盘,启动开发板,插入 U 盘,加载U 盘,运行程序如下。

 

如上图所示,可以看到执行了 helloexec 的打印函数,调用成功。

24.4 实验-fork创建新进程

在 linux 中可以使用 fork 创建和当前进程一样的进程,新的进程叫子进程,原来的进程叫父进程。

使用 man 学习 fork 进程

如下图所示,使用命令“man 2 fork”。

 

如下图所示,函数 fork。 

 

接着注意一下相关的函数,如下图所示。 

接着介绍一下 fork 的用法。

pid_t fork(void);

参数:无

返回值:执行成功,子进程 pid 返回给父进程,0 返回给子进程;出现错误-1,返回给父进程。执行失败的唯一情况是内存不够或者 id 号用尽,不过这种情况几乎很少发生。

进程函数 fork 的返回值

系统函数 fork 调用成功,会创建一个新的进程,它几乎会调用差不多完全一样的 fork 进程。

子进程的 pid 和父进程不一样,是新分配的。

子进程的 ppid 会设置为父进程的 pid,也就是说子进程和父进程各自的“父进程”不一样。

子进程中的资源统计信息会清零。

挂起的信号会被清除,也不会被继承(后面章节进程通信中会介绍信号)。所有文件锁也不会被子进程继承。

这里有一个很难理解的地方是,fork 函数的返回值问题。

fork 函数执行成功,子进程 pid 返回给父进程,0 返回给子进程;

出现错误-1,返回给父进程。执行失败的唯一情况是内存不够或者 id 号用尽,不过这种情况几乎很少发生。

函数例程

编写简单的 fork.c 文件测试 fork 函数。首先添加头文件,如下图所示。

 

main 函数,部分代码如下图所示。 

 

main 函数,如下图所示。 

 

如上图代码所示。

第 1-4 行:头文件。

第 6-9 行:定义变量。

第 10-27 行:调用 fork 函数。如果出错返回-1,打印 fork failed!;如果成功,则给父进程返回子进程号,同时给子进程返回父进程号。

编译运行测试

在 Ubuntu 系统下,如下图所示,进入前面实验创建的目录“/home/linuxsystemcode/exe”,将源码 fork.c 拷贝进去,如下图所示。

 

使用命令“arm-none-linux-gnueabi-gcc -o fork fork.c -static”编译 fork 文件,如下图所示,使用命令“ls”可以看到生成了 fork 可执行文件。 

这里介绍通过 U 盘拷贝来运行代码的方法,也可以编译进文件系统或者使用 NFS 文件系统等等

将编译成的可执行文件 fork,拷贝到 U 盘,启动开发板,插入 U 盘,加载 U 盘,运行程序如下。

 

如上图所示,可以看到打印的父进程和子进程号,然后其中打印的 i 都是一样的。子进程可以使用父进程中定义的变量,和父进程中的变量却是不同的变量。

进程的调用

生成进程的 fork 函数对于初学者是个难点,看上去它的行为不符合常规,而这也正好体现了操作系统多任务的特点。

网上关于进程这部分的博文,也有很多人提出疑问,如下图所示。

 

还有类似下面疑惑。 

其实在介绍很多知识点的时候,用单片机或者裸机的概念去理解都是没有太多问题的,但是到了这里,很容易出现一些无法理解的情况。

对于初学者来讲,理解‘fork’函数是比较困难的,因为 fork 以后程序会分岔,会沿着两条不同的路径去执行,这跟我们以往‘程序是顺序执行的’这样的思想似乎有点相悖。实际上,这个函数执行以后系统会生成另一个进程,新的进程会和父进程并列,并接受时间片的调度。说到底,程序仍然是在顺序执行的。

24.5 实验-进程终止exit

这个函数大家肯定已经用过很多次了,就是 exit,在 main 函数的结尾会使用 return 或者 exit 结束程序。当使用 exit 的时候,就是使用的进程终止函数 exit。

使用 man 学习 exit 函数

如下图所示,使用命令“man 2 exit”。

 

如下图所示,有一些相关的函数 execve(2), exit_group(2), fork(2), kill(2), wait(2)等等。 

接着介绍一下 void exit(int status)的用法。

void exit(int status)

参数:返回给父进程的参数。

返回值:无。

函数_exit 和 exit 没什么区别,不需要花太多时间去追究。

创建进程还有一个 vfork 函数,感兴趣的可以通过网络找个例子实现以下,vfork 的使用会产生很多新问题,使用 vfork 创建的进程结束需要调用_exit。

Linux 命令的本质

在这一段时间的学习中,Linux 命令大家肯定用的比较熟练了,不会再有陌生的感觉了。其实这些命令都是一些小程序,大家感兴趣也可以看看这些小程序的源码。

exec 函数族可以执行新程序,那么肯定也能运行这些“Linux 命令”小程序。

24.6 实验exec函数族综合例程

有了 exec 函数族基础知识,为了加深大家对 linux 命令和 exec 函数族的理解,结合起来给大家提供一个小例程。

编写简单的 execls.c 文件。

首先添加头文件,如下图所示。

 

main 函数部分代码,如下图所示。 

 

main 函数部分代码,如下图所示。

main 函数部分代码,如下图所示。 

main 函数部分代码,如下图所示

如上图代码所示。

第 1-5 行:头文件

第 6-76 行:分别使用 exec 函数族实现运行 ls 命令的功能

编译运行测试

在 Ubuntu 系统下,如下图所示,进入前面实验创建的目录“/home/linuxsystemcode/exe”,将源码 execls.c 拷贝进去,进入新建的文件夹 exe,如下图所示。

 

使用命令“arm-none-linux-gnueabi-gcc -o execls execls.c -static”编译 execls 文件,如下图所示,使用命令“ls”可以看到生成了 open 可执行文件。 

 

这里介绍通过 U 盘拷贝来运行代码的方法(可参考 10.3.5 小节),也可以编译进文件系统或者使用 NFS 文件系统等等

将编译成的可执行文件 execls,拷贝到 U 盘,启动开发板,插入 U 盘,加载 U 盘,运行程序如下。

先把 execls 文件拷贝到当前目录下,使用 ls 命令,查看当前目录,便于和后面执行程序之后对比。

 

运行后部分结果如下图所示。 

 

如上图所示,可以和前面执行 ls 命令之后对比。 


http://www.kler.cn/a/417238.html

相关文章:

  • 【CSS in Depth 2 精译_061】9.4 CSS 中的模式库 + 9.5 本章小结
  • 【WRF后处理】WRF模拟效果评价及可视化:MB、RMSE、IOA、R
  • 【JVM什么时候触发YoungGC和FullGC】
  • centos8:Could not resolve host: mirrorlist.centos.org
  • ICPM端口的用途是什么?
  • NAT拓展
  • mind+自定义库编写注意事项
  • Gradle vs. Maven: 到底哪个更适合java 项目?
  • LeetCode 力扣 热题 100道(十一)字母异位词分组(C++)
  • 力扣_876. 链表的中间结点
  • UE5 打包报错 Unknown structure 的解决方法
  • 入门产品经理,考PMP还是NPDP?
  • 在windows上安装sqlite
  • ENSP IPV6-over-IPV4
  • windows11 使用体验记录
  • webpack(react)基本构建
  • 实习冲刺第三十六天
  • 【React】React 组件通信:多种方式与最佳实践
  • shodan2-批量查找CVE-2019-0708漏洞
  • OceanBase数据库系列之:基于docker快速安装OceanBase数据库,基于linux服务器快速部署OceanBase数据库
  • 2025年Java面试八股文大全
  • 非线性模型预测控制(NMPC)算法及其Python实现
  • OpenCV 模板匹配全解析:从单模板到多模板的实战指南
  • Keil 5, Flash Timeout. Reset the Target and try it again.
  • Spire.PDF for .NET【页面设置】演示:旋转 PDF 中的页面
  • Docker化部署Django:高效、可扩展的Web应用部署策略