进程

进程环境


1 进程环境

1.1 main函数

C程序总是从main函数开始执行,其原型为:
int main(int argc, char *argv[]);
其中,argc是命令行参数的数目,argv是指向参数的各个指针构成的数组。

1.2 进程终止

有8种方式使进程终止,其中5种为正常终止:
(1)从main函数返回。
(2)调用exit。
(3)调用_exit或_Exit。
(4)最后一个线程从其启动例程返回。
(5)最后一个线程调用pthread_exit。
3种异常终止:
(1)调用abort。
(2)接到一个信号并终止。
(3)最后一个线程对取消请求做出响应。
atexit函数

#include <stdlib.h>
int atexit(void (*func)(void));
成功返回0,出错返回非0

参数是一个函数地址。
注意,内核使程序执行的唯一方法是调用一个exec函数,进程自愿终止的唯一方法是显示或隐式地(通过调用exit)调用_exit或_Exit。

1.3 命令行参数

执行一个程序时,调用exec的进程可将命令行参数传递给该新程序。

1.4 环境表

每个程序都会接收到一张环境表,环境表是一个字符指针数组,每个指针包含一个以null结尾的字符串的地址,全局变量environ则包含了该指针数组的地址,extern char **environ;
称environ为环境指针,指针数组为环境表,其中各指针指向的字符串为环境字符串。
环境由name=value这样的字符串组成,名字大多完全由大写字母组成。

1.5 环境变量

可用函数getenv取环境变量值。

#include <stdlib.h>
char *getenv(const char *name);
返回指向与name关联的value的指针,未找到则返回NULL

环境表函数

#include <stdlib.h>
int putenv(char *str);
int setenv(const char *name, const char *value, int rewrite);
int unsetenv(const char *name);
成功返回0,出错返回非0
  • putenv:取形式为name=value的字符串放到环境表中。
  • setenv:将name设置为value。
  • unsetenv:删除name的定义。

2 进程概述

2.1 进程定义

进程是一个程序的一次执行的过程,也是资源分配的小单元。它和程序是有本质区别的,程序是静态的,它是一些保存在磁盘上的指令的有序集合, 没有任何执行的概念;而进程是一个动态的概念,它是程序执行的过程,包括了动态创建、调度和消亡的整 个过程,是程序执行和资源管理的小单位。

2.2 进程标识

每个进程都有一个非负整数表示的唯一进程ID,但进程ID可以重用(当该进程终止时)。主要的进程标识有进程号(PID)和它的父进程号(PPID)。其中 PID 惟一地标识一个进程。PID 和 PPID 都是非零的正整数。
系统中的专用进程:
调度进程:ID为0,也称交换进程(swapper),是内核的一部分,不执行磁盘上的任何程序,也称系统进程。
init进程:ID为1,由内核调用,负责在自举内核后启动一个UNIX系统,绝不会终止,是一个以超级用户特权运行的普通用户进程。
页守护进程:ID为2,负责支持虚拟存储系统的分页操作。

#include <unistd.h>
pid_t getpid(void);   //返回调用进程的进程ID
pid_t getppid(void);  //返回调用进程的父进程ID
uid_t getuid(void);   //返回调用进程的实际用户ID
uid_t geteuid(void);  //返回调用进程的有效用户ID
gid_t getgid(void);   //返回调用进程的实际组ID
gid_t getegid(void);  //返回调用进程的有效组ID
2.3 进程运行状态

进程根据它的生命周期可以划分成 3 种状态。

执行态:该进程正在运行,即进程正在占用 CPU。

就绪态:进程已经具备执行的一切条件,正在等待分配 CPU 的处理时间片。

等待态:进程不能使用 CPU,若等待事件发生(等待的资源分配到)则可将其唤醒。

2.4 进程结构

Linux 系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。每个进程都是 一个独立的运行单位,拥有各自的权利和责任,运行 在独立的虚拟地址空间。

Linux 中的进程包含 3 个段。

数据段存放的是全局变量、常数以及动态数据 分配的数 据空间,根据存放的数据,数据段又可以分成普通 数据段(包 括可读可写/只读数据段,存放静态初始化的全局 变 量 或 常 量) 、BSS 数据段(存放未初始化的全局变量)以 及堆(存放 动态分配的数据) 。

代码段存放的是程序代码的数据。

堆栈段存放的是子程序的返回地址、子程序的参 数以及程 序的局部变量等。

2.5 进程模型

在 Linux 系统中,进程的执行模式划分为用户模式和内核模式。。如果当前运行的是用户程序、应用程序或者 内核之外的系统程序,那么对应进程就在用户模式下运行;如果在用户程序执行过程中出现系统调用或者发 生中断事件,那么就要运行操作系统(即核心)程序,进程模式就变成内核模式。在内核模式下运行的进程 可以执行机器的特权指令,而且此时该进程的运行不受用户(包括root用户)的干扰。

2.6 进程管理

手工启动:手工启动进程又可分为前台启动和后台启动。

调度启动:指定任务运行的时间或者场合,系统就会自动完成这一切工作。

3 进程控制

3.1 fork函数

Linux 中创建一个新进程的惟一方法是使用fork函数。fork函数用于从已存在的进程中创建一个新进程。新进程称为子进程,而原进程称为父进程。子进程从父进程处继承了整个进程的地址空间,包括进程上下文、 代码段、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目 录、根目录、资源限制和控制终端等,而子进程所独有的只有它的进程号、资源使用和计时器等。 父子进程分别获得其所属 fork的返回值,其中在父进程中的返回值是子进程的进程号,而在子进程中返回0。

#include <unistd.h>
pid_t fork(void);
//子进程中返回0,父进程中返回子进程ID,出错返回-1

fork完整地复制了父进程的整个地址空间,执行速度较慢,vfork也能创建新进程,但它不产生父进程的副本。它是通过允许父子进程可访问相同物理内存从而伪装了对进程地址空间的真实拷贝,当子进程需要改变内存中数据时才复制父进程,这称为“写操作时复制”(copy-on-write)技术。

3.2 exec函数族

exec 函数族就提供了一个在进程中启动另一个程序执行的方法,它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,Linux 中使用 exec 函数族主要有两种情况。这 6 个函数中真正的系统调用只有 execve。

(1)当进程不能再为系统和用户做出任何贡献时,就可以调用exec函数族中的任意一个函数让自己重生。

(2)如果一个进程想执行另一个程序,可调用 fork()函数新建一个进程,然后调用exec函数族中的任一函数。

#include <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 *path, char *const argv[], char *const envp[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
//成功返回0,出错返回-1

查找方式:前4个函数的查找方式都是完整的文件目录路径,而后2个函数(也就是以p结尾的两个函数)可以只给出文件名,系统就会自动按照环境变量$PATH所指定的路径进行查找。

参数传递方式: exec 函数族的参数传递有两种方式:函数名的第 5 位字母来区分的,字母为l(list)的表示逐个列举参数的方式,字母为v(vertor)的表示将所有参数整体构造指针数组传递。这里的参数实际上就是用户在使用这个可执行文件时所需的全部命令选项字符串(包括该可执行程序命令本身),这些参数必须以 NULL 表示结束。

环境变量:exec 函数族可以默认系统的环境变量,也可以传入指定的环境变量。这里以e(environment)结尾的两 个函数 execle()和 execve()就可以在 envp[]中指定当前进程所使用的环境变量。

前4位统一为:exec
第5位l(execl、execle、execlp)参数传递为逐个列举方式
v(execv、execve、execvp)参数传递为构造指针数组方式
第6位e可传递新进程环境变量
p可执行文件查找方式为文件名

注意:exec 很容易执行失败,定要加上错误判断语句,常见的原因有:

(1)找不到文件或路径,此时 errno 被设置为 ENOENT。

(2)数组 argv 和 envp 忘记用 NULL 结束,此时 errno 被设置为 EFAULT。

(3)没有对应可执行文件的运行权限,此时 errno 被设置为 EACCES。

3.3 exit和_exit函数

exit和__exit函数都是用来终止进程的。当程序执行到 exit或_exit时,进程会无条件地停止剩下的所有操作,清除包括 PCB 在内的各种数据结构,并终止本进程的运行。exit函数与_exit函数最大的区别就在于 exit()函数在调用 exit 系统之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,即清理 I/O 缓冲。若想保证数据的完整性,就一定要使用 exit函数。

#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);

status:一个整型的参数,可以利用这个参数传递进程结束时的状态。0 表示正常结束,其他的数值表示出现了错误,进程非正常结束。

僵尸进程(Zombie):在一个进程调用了 exit()之后,该进程并不会立刻完全消失,而是留下一个称为僵尸进程的数据结构。僵尸进程是一种非常特殊的进程,它已经放弃 了几乎所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表 中保留一个位置,记载该进程的退出状态等信息供其他进程收集。

3.4 wait和waitpid函数

wait:用于使父进程(也就是调用 wait()的进程)阻塞,直到一个子进程结束或者该进程接到了一个 指定的信号为止。如果该父进程没有子进程或者他的子进程已经结束,则 wait()就会立即返回。

waitpid:作用和 wait()一样,但它并不一定要等待第一个终止的子进程,它还有若干选项,如可提供一个非阻塞版本的 wait功能,也能支持作业控制。

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
//成功返回已经结束运行的子进程的进程号,使用选项WNOHANG且没有子进程退出返回0,出错返回-1

status:一个整型指针,是该子进程退出时的状态 。

pid

pid > 0:只等待进程 ID 等于 pid 的子进程,不管已经有其他子进程运行 结束退出了,只要指定的子进程还没有结束。waitpid就会一直等下去。

pid = -1:等待任何一个子进程退出,此时和 wait()作用一样。
pid = 0:等待其组 ID 等于调用进程的组 ID 的任一子进程。

pid < -1:等待其组 ID 等于 pid 的绝对值的任一子进程 。

options

WNOHANG:若由 pid 指定的子进程不立即可用,则 waitpid不阻塞,此时返回值为 0 。

WUNTRACED:若实现某支持作业控制,则由 pid 指定的任一子进程 状态已暂停,且其状态自暂停以来还未报告过,则返回其状态 。

0:同 wait,阻塞父进程,等待子进程退出。

4 守护进程

4.1 概述

守护进程,也就是通常所说的 Daemon 进程,是 Linux 中的后台服务进程。。它是一个生存期较长的进程, 通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。Linux 大多数系统服务都是通过守护进程实现的。如果想让某个进 程不因为用户、终端或者其他的变化而受到影响,那么就必须把这个进程变成一个守护进程。

4.2 编写守护进程

(1)创建子进程,父进程退出:在 shell 终端里造 成一种程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在 shell 终端里则可以执行其 他的命令,从而在形式上做到了与控制终端的脱离。

孤儿进程:父进程已经先于子进程退出,会造成子进程没有父进程,从 而变成一个孤儿进程。孤儿进程会自动由 init 进程收养,变成 init 进程的子进程。

(2)在子进程中创建新会话:使用 的是系统函数setsid。

进程组:一个或多个进程的集合。进程组由进程组 ID 来惟一标识。除了进程号(PID)之外,进程组 ID 也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组 ID。

会话期:一个或多个进程组的集合。通常,一个会话开始于用户登 录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。

setsid函数:用于创建一个新的会话,并担任该会话组的组长。让进程摆脱原会话、原进程组、原控制终端的控制。 调用 fork()函数时,子进程全盘复制了父进程的会话期、进程组和控制终端等,虽然父进程退出了,但原先的会话期、进程组和控制终端等并没有改变,不是真正意义上的独立。

#include <sys/types.h>
#include <unistd.h>
pid_t setsid(void);
//成功返回该进程组ID,出错返回-1

(3)改变当前目录为根目录:是让“/”作为守护进程的当前工作目录,使用chdir函数。

(4)重设文件权限掩码:文件权限掩码是指屏蔽掉文件权限中的对应位。把文件权限掩码设置为 0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask。

(5)关闭文件描述符:子进程会从父进程那里继承一些已经打开了的文件,守护进程已经与所属的控制终端失去了联系,所以文件描述符 为 0、1 和 2 的 3 个文件也应被关闭。

4.3 守护进程的出错处理

使用 syslog 服务,将程序中的出错信息输入到系统日志文件中(例如:“/var/log/messages”),从而可以直观地看到程序的问题所在。syslog是 Linux 中的系统日志管理服务,通过守护进程 syslogd 来维护。

增强该守护进程的灵活性。设置文件权限掩码的函数是umask。

(5)关闭文件描述符:子进程会从父进程那里继承一些已经打开了的文件,守护进程已经与所属的控制终端失去了联系,所以文件描述符 为 0、1 和 2 的 3 个文件也应被关闭。

4.3 守护进程的出错处理

使用 syslog 服务,将程序中的出错信息输入到系统日志文件中(例如:“/var/log/messages”),从而可以直观地看到程序的问题所在。syslog是 Linux 中的系统日志管理服务,通过守护进程 syslogd 来维护。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值