第二阶段Linux系统编程之进程

本文详细介绍了Linux系统中的进程概念,包括进程的定义、查看进程的方法、进程标识符及其作用。重点讨论了进程的创建,如fork和vfork函数,以及创建过程中的写实拷贝技术。此外,还讲解了进程的退出方式、等待子进程退出的wait函数家族,以及exec、system和popen函数在进程执行和通信中的应用。通过这些内容,读者能深入理解Linux进程的生命周期和管理机制。
摘要由CSDN通过智能技术生成

一、进程的关键概念:

问1:什么是程序,什么是进程,有什么区别

  • 程序是静态的概念,gcc xxx.c -o pro 磁盘中生成pro文件叫进程
  • 进程是程序的一次运行活动
  • 通俗点讲就是程序跑起来了,系统中就多了一个进程

问2:如何查看系统中有那些进程

a、 使用ps指令查看。ps-aux : 查看所有进程。
在实际工作中,配合grep来查找程序中是否存在某一个进程。
例子:

ps-aux |grep int //只看int进程。

b、 使用top指令查看、类似windows任务管理器,它可以查看内存的使用情况及cpu的占有率。

问3:什么是进程标识符
进程标识符: 每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证。
pid = 0: 称为交换进程(swapper)
作用: 进程调度,每个进程的运行都有它的干涉。
pid = 1: init进程
作用: 系统初始化。
标识符的获取: 编程调用getpid函数获取自身的进程标识符,getppid获取父进程的进程标识符。

问4:什么叫父进程,什么子进程
进程A创建了进程B
那么A叫做父进程,B叫做子进程。
父子进程是相对的概念,理解为人类中的父子关系。

问5:c程序的存储空间是如何分配?

在这里插入图片描述

  • 正文段。这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是频繁执行的程序(如文本编辑器、C编译器和shell等)在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其自身的指令。
  • 初始化数据段。通常将此段称为数据段,它包含了程序中需明确地赋初值的变量。例如,C程序中出现在任何函数之外的声明:int maxcount = 99; 使此变量带有其初值存放在初始化数据段中。
  • 非初始化数据段。通常将此段称为bss段,这一名称来源于一个早期的汇编运算符,意思是“block started by symbol”(由符号开始的块),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。出现在任何函数外的C声明 long sum【1000】; 使此变量存放在非初始化数据段中。
  • 栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次调用函数时,其返回地址以及调用者的环境信息(例如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,可以递归调用C函数。递归函数每次调用自身时,就使用一个新的栈帧,因此一个函数调用实例中的变量集不会影响另一个函数调用实例中的变量。
  • 堆。通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于非初始化数据段和4:56战之间。

二、创建进程函数fork的使用

使用fork函数创建一个进程:

pid_t fork(void);

返回值:
fork函数调用成功,返回两次。

  • 返回值为0,代表当前进程是子进程。
  • 返回值为非负数,代表当前进程为父进程。
  • 调用失败,返回-1。

三、进程创建发生了什么事

  • 在早期Linux内核中如果创建了子进程会把正文、被初始化的数据、堆、栈、命令行参数和环境变量、一些文件、来进行拷贝。只有代码段会共享。
  • 现在的写实拷贝是只有量在发生改变时,才会进行拷贝。

四、创建新进程的实际应用场景及fork总结

fork创建一个子进程的一般目的:

  • (1) 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
  • (2)一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec (我们将在8.10节说明exec)。

fork实战总结:

  • 由fork创建的新进程被称为子进程(child process)。fork函数被调用一次,但返回两次。两次返回的唯一区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID。fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID(进程ID 0总是由内核交换进程使用,所以一个子进程的进程ID不可能为0)。
  • 子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。注意,这是子进程所拥有的副本。父、子进程并不共享这些存储空间部分。父、子进程共享正文段(见7.6节)。
  • 由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全复制。作为替代,使用了写实复制(Copy-On-Write,COW)技术。这些区域由父、子进程共享,而且内核将它们的访问权限改变为只读的。如果父、子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统中的一“页”。Bach[1986]的9.2节和McKusick等[1996]的5.6节和5.7节对这种特征做了更详细的说明。

五、vfork创建进程

vfork函数:
也可以创建进程,与fork有什么区别。
关键区别一:
vfork直接使用父进程存储空间,不拷贝。
关键区别二:
vfork保证子进程先运行,当子进程调用exit()退出后,父进程才执行。

六、进程退出

正常退出:

  1. main函数调用return。
  2. 进程调用exit(),标准c库。
  3. 进程调用_exit()或者_Exit()属于系统调用。
  4. 进程最后一个线程返回。
  5. 最后一个线程调用pthreed_exit。

异常退出:

  1. 调用abort。
  2. 当进程收到某些信号时,如ctr+c。
  3. 最后一个线程对取消(cancellation)请求做出响应。

进程退出的整体讲解:

  • 不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。

  • 对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit、_exit和_Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数(在下一节说明)取得其终止状态。

推荐使用exit()因为它会对缓冲区做一些处理,而_exit()或_Exit()不会。

七、等待子进程退出

父进程等待子进程退出,并收集子进程的退出状态:

  • 等待用wait()函数,它会收到exit()返回的状态码,而状态码的解析需要下面几个宏去做。

等待函数:

pid_t wait(int *status);
pid_t waitpid(pid_t pid,int *status,int options);
int waitid(idtype_t idtype,id_t id,siginfo_t *infop,int options);

status参数:

  • 是一个整型数指针。
  • 非空:子进程退出状态放在它所指向的地址中。
  • 空:不关心退出状态。

函数讲解:

  • 如果其所有子进程都还在运行,则阻塞。
  • 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
  • 如果它没有任何子进程,则立即出错返回。

检查wait和waitpid所返回的终止状态的宏:

说明
WIFEXITED(status)若为正常终止子进程返回的状态,则为真,对于这种情况可执行WEXITSTATUS(status), 取子进程传送给exit(),_exit()或_Exit()参数的低8位
WIFSIGNALED(status)若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号)。对于这种情况,可执行WTERMSTG(status),取使子进程终止的信号编号。另外,有些实现(非Single UNIX Specification)定义宏WCOREDUMP(status),若已产生终止进程的core文件,则它返回真。
WIFSTOPPED(status)若为当前暂停子进程的返回状态,则为真,对于这种情况,可执行WSTOPSIG(status)取使子进程暂停的信号编号。
WIFCONTINUED(status)若在作业控制暂停后已经继续的子进程返回了状态,则为真。(POSIX.I的XSI扩展,仅用于waitpid)
  1. (exit(3))status是一个整型的地址,int *p = 3?不一定 = 3,什么时候等于3呢。如果正常退出,要用WIFEXITED(*p)这个宏给它加工一下才能知道这个3。
  2. 子进程退出状态不被收集,变成僵死进程(俗称僵尸进程)。

wait与waitpid区别:
wait使用者阻塞,waitpid有一个选项,可以使调用者不阻塞。

对于waitpid函数中pid参数的作用解释如下:

  • pid == -1: 等待任一子进程。就这一方面而言,waitpid与wait等效。
  • pid > 0: 等待其进程ID与pid相等的子进程。
  • pid == 0: 等待其组ID等于调用进程ID的任一子进程。
  • pid < -1: 等待其组ID等于pid绝对值的任一子进程。

waitpid的options常量:

常量说明
WCONTINUED WNOHANG WUNTRACED1、若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态(POSIX.1的XSI扩展)。 2、若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0。3、若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态、并且其状态自暂停以来还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程。

孤儿进程:

  1. 父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。
  2. Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。

九、exec函数

这是一篇介绍exec函数精彩的博文:https://blog.csdn.net/u014530704/article/details/73848573?utm_source=blogxgwz1

环境变量:
环境变量博文介绍:
https://blog.csdn.net/qingkongyeyue/article/details/52733203

  • 单独查看PATH环境变量(echo $PATH)。
  • 环境变量的作用是让系统能找到当前路径下的可执行性程序。
  • 修改(配置)环境变量:用export $PATH = 原先的环境变量(echo path查出来的)或者export $PATH = $PATH:当前路径。{这样在别的目录下也可以执行之前目录下的程序了。}

十、system函数

system函数博文讲解:
https://blog.csdn.net/yankai0219/article/details/6730121

  • system就是执行一条shell指令。
  • system实际就是分装后的execl。

执行指令的两种方式:
1、./程序名
2、sh -c./程序名

返回值:
system 在调用/bin/sh时失败则返回127,其他失败原因返回-1。成功则返回进程的状态值。

system与exec族函数区别:
system执行完后,还会执行后面的代码。

十一、popen函数

函数原型:

#include<stdio.h>
FILE *popen(const char *command,const char *type);
int pclose(FILE *stream);

popen函数讲解:
https://blog.csdn.net/libinbin_1014/article/details/51490568

popen比system在应用中的好处:
用popen可以获取运行的输出结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值