前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux进程编程

Linux进程编程

作者头像
Marigold
发布2022-06-17 14:34:24
7.8K0
发布2022-06-17 14:34:24
举报
文章被收录于专栏:MarigoldMarigold

Linux进程编程

3.1 fork系统调用

3.1.1 fork工作原理

fork系统调用是创建子进程的唯一方法。执行过程如下:

  1. Linux内核在进程表中为子进程分配一个表项,然后分配PID。子进程表项的内容来自父进程,fork会将父进程的表项复制为副本,并分配给子进程;
  2. Linux内核使父进程的文件表和索引表的节点自增1,创建用户及上下文;
  3. 将父进程上下文复制到子进程上下文空间中;
  4. fork调用结束后子进程的PID将返回给父进程,而子进程获得的值为0。

3.1.2 fork函数

头文件:unistd.h

函数原型:pid_fork(void)

功能:创建一个与原来进程几乎完全相同的进程,即两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork函数后,系统先给新的进程分配资源,例如,存储数据和代码的空间。然后把原来的进程所有值都复制到新的进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

返回值:fork被调用一次却能够返回两次且可能有三种不同的返回值:

  1. 在父进程中,fork返回新创建子进程的进程ID(通常为父进程PID+1);
  2. 在子进程中,fork返回0;
  3. 如果出现错误,fork返回一个负值。 fork出错可能有两种原因:
  4. 当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN;
  5. 系统内存不足,这时errno的值被设置为ENOMEM。 两个进程有不同的PID,但执行顺序由进程调度策略决定。

3.1.3 fork编程示例

  1. 打开Ubuntu终端,切换用户到root,新建一个process文件夹用于存放实验文件,进入该目录下,输入sudo vi forkProcess.c使用vi文本编辑器编辑forkProcess.c文件;
image.png
image.png
  1. 按下i键进入编辑模式,输入fork编程示例,该示例创建一个子进程,通过fork()函数返回值判断进程是子进程还是父进程,并打印信息。
image.png
image.png
  1. 按下ESC键退出编辑模式,输入“:wq”回到命令行,使用gcc编译器编译forkProcess.c文件生成可执行文件forkProcess.
image.png
image.png
  1. 输入./forkProcess执行该文件,得到结果如下:
image.png
image.png

3.1.4 小结

其实,fork底层是调用了内核的函数来实现fork的功能的,即先create()先创建进程,此时进程内容为空,然后clone()复制父进程的内容到子进程中,此时子进程就诞生了,接着父进程就return返回了。而子进程诞生后,是直接运行return返回的,然后接着执行后面的程序,这里注意:子进程是不会执行前面父进程已经执行过的程序了得,因为PCB中记录了当前进程运行到哪里,而子进程又是完全拷贝过来的,所以PCB的程序计数器也是和父进程相同的,所以是从fork()后面的程序继续执行。此时就按照前面的规则进行判断返回[1]。如下图所示:

image.png
image.png

3.2 exec系统调用

3.2.1 exec函数族作用

exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,颇有些神似"三十六计"中的"金蝉脱壳"。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。

我们应该明白了,Linux下是如何执行新程序的,每当有进程认为自己不能为系统和用户做出任何贡献了,他就可以发挥最后一点余热,调用任何一个exec,让自己以新的面貌重生;或者,更普遍的情况是,如果一个进程想执行另一个程序,它就可以fork出一个新进程,然后调用任何一个exec,这样看起来就好像通过执行应用程序而产生了一个新进程一样[2]。

3.2.2 exec函数族

头文件:unistd.h

函数原型:

int execl(const char *path, const char *arg, …);

int execv(const char *path, char *const argv[]);

int execle(const char *path, const char *arg,…, char * const envp[]);

int execve(const char *file, char *const argv[]);

int execlp(const char *file, const char *arg, …);

int execvp(const char *file, char *const argv[]);

选项:

​ l:希望接收一个以逗号分隔的参数列表,列表以NULL指针作为结束标志;

​ v:希望接收一个以NULL结尾的字符串数组的指针;

​ p:是一个以NULL结尾的字符串数组指针,函数可以利用PATH变量查找子程序文件;

​ e:函数传递指定参数envp,允许改变子进程的环境,无后缀e时子进程使用当前程序的环境。

参数:

​ path:可执行文件的路径名字;

​ arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束;

​ file:如果参数file中包含/,就将其视为路径名,否则就按PATH环境变量,在它所指定的各目录中搜寻可执行文件。

功能:

以新进程代替原有进程,但PID保持不变。

返回值:

执行成功不返回,出错则返回-1,失败原因记录在errno中。

六个函数的区别:

  1. 查找方式不同:前四个函数的查找方式都是完整的文件目录路径,而后两个(以p结尾的两个函数)可以只给出文件名,系统会自动从环境变量“$PATH”所指的路径中进行查找;
  2. 参数传递方式不同:exec函数族的参数传递有两种方式:函数名的第五位字母为“l”(list)表示逐个列举的方式、函数名第五位字母为“v”(vector)的表示将所有参数整体构造成指针数组传递。然后将该数组的首地址当作参数传给它,数组中的最后一个指针要求是NULL;
  3. 环境变量不同:以“e”(environment)结尾的两个函数execl、execve可以在envp[]中指定当前进程所使用的的环境变量替换掉该进程继承的环境变量,其他函数把调用进程的环境传递给新进程。

3.2.3 exec编程示例

execl 实现ls指令

image.png
image.png
image.png
image.png

execv 实现获取系统时间

image.png
image.png
image.png
image.png

3.2.4 小结

执行exec系统调用,一般都是这样,用fork()函数新建立一个进程,然后让进程去执行exec调用。我们知道,在fork()建立新进程之后,父进程与子进程共享代码段,但数据空间是分开的,但父进程会把自己数据空间的内容copy到子进程中去,还有上下文也会copy到子进程中去。而为了提高效率,采用一种写时copy的策略,即创建子进程的时候,并不copy父进程的地址空间,父子进程拥有共同的地址空间,只有当子进程需要写入数据时(如向缓冲区写入数据),这时候会复制地址空间,复制缓冲区到子进程中去。从而父子进程拥有独立的地址空间。而对于fork()之后执行exec后,这种策略能够很好的提高效率,如果一开始就copy,那么exec之后,子进程的数据会被放弃,被新的进程所代替。

3.3 exit系统调用

3.3.1 exit工作原理

exit系统调用的执行发生以下事件:

  1. 清除当前进程的所有信号处理函数。
  2. 如果当前进程是和终端关联的“进程组组长”,则会向每个组内进程发送hang-up signal,并且把这些成员的进程组设置为0。
  3. 通过内核内部算法关闭当前进程所有打开的文件描述符,并且释放当前目录所关联的inode;如果存在current (charged)root,也将其释放通过算法iput。
  4. 为进程释放所有的region以及关联的memory。
  5. 计算进程机器子进程执行的时间(user mode 和kernel mode),并把记录写到一个全局的accounting file。
  6. 将进程的状态改变为zombie,并将自己的所有的子进程的父进程ID设置为1(init);如果有孩子的状态是zombie,向init进程发SIGCHLD信号,以清除子进程的process table slot。
  7. exiting进程向自己的父进程发送SIGCHLD信号。
  8. 进行context switch,调度其他非zombie进程(本进程已经是zombie)。

3.3.2 exit函数

函数原型1:void _exit(int status)

头文件:unistd.h

函数原型2:void exit(int status)

头文件:stdlib.h

功能:终止发出调用的进程,status是返回给父进程的状态值,父进程可通过wait系统调用获得。

exit()和_exit()的区别:

l _exit()的作用最简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;

l exit()在终止进程之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,即清理“I/O缓冲”;

l 两者最终都要将控制权交给内核,旖旎次,要想保证数据的完整性,就一定要使用exit()。

3.4 wait系统调用

3.4.1 wait工作原理

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

3.4.2 wait函数

头文件:sys/types.h和sys/wait.h

函数原型1:pid_t wait(int *status)

函数原型2:pid_t waitpid(pid_t pid, int *status, int options)

参数:

status:返回子进程退出时的状态;

pid:pid>0时:等待进程号为pid的子进程结束、pid=0时:等待组ID等于调用进程组ID的子进程结束、pid=-1时:等待任一子进程结束,等价于调用wait()、pid<-1时:等待组ID等于PID的绝对值的任一子进程结束;

options:WNOHANG:若pid指定的子进程没有结束,则waitpid()不阻塞而立即返回,此时的返回值为0、WUNTRACED:为了实现某种操作,由pid指定的任一进程已被暂停,且其状态自暂停以来还未报告过,则返回其状态、0:同wait(),阻塞父进程,等待子进程退出。

wait()与waitpid()区别:wait()函数等待所有子进程的僵死状态,waitpid()函数等待PID与参与pid相关的子进程的僵死状态。

检查子进程的返回状态码status:

WIFEXITED(status):进程中通过调用_exit()或exit()正常退出,该宏值为非0;

WIFSIGNALED(status):子进程因得到的信号没有被捕捉导致退出,该宏值为非0;

WIFSTOPPED(status):子进程没有终止但停止了,并可重新执行时,该宏值为非0,这种情况仅出现在waitpid()调用中使用了WUNTRACED选项;

WEXITSTATUS(status):如果WIFEXITED(status)返回非0,该宏返回由子进程调用_exit(status)或exit(status)时设置的调用参数status值;

WTERMSIG(status):如果WIFSIGNALED(status)返回非0,该宏返回导致子进程退出的信号的值;

WSTOPSIG(status):如果WIFSTOPPED(status)返回非0,该宏返回导致子进程停止的信号的值。

3.4.3 进程管理综合示例

image.png
image.png
image.png
image.png
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-12-16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Linux进程编程
  • 3.1 fork系统调用
    • 3.1.1 fork工作原理
      • 3.1.2 fork函数
        • 3.1.3 fork编程示例
          • 3.1.4 小结
          • 3.2 exec系统调用
            • 3.2.1 exec函数族作用
              • 3.2.2 exec函数族
                • 3.2.3 exec编程示例
                  • 3.2.4 小结
                  • 3.3 exit系统调用
                    • 3.3.1 exit工作原理
                      • 3.3.2 exit函数
                      • 3.4 wait系统调用
                        • 3.4.1 wait工作原理
                          • 3.4.2 wait函数
                            • 3.4.3 进程管理综合示例
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档