1.进程概念
进程是管理事务的基本单元
2.并发并行
- 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。
- 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
3.进程控制块PCB
进程运行时,内核为进程每个进程分配一个PCB(进程控制块),维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。
-
进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
-
进程的状态,有就绪、运行、挂起、停止等状态。
-
进程切换时需要保存和恢复的一些CPU寄存器。
-
描述虚拟地址空间的信息。
-
描述控制终端的信息。
-
当前工作目录(Current Working Directory)。
-
umask掩码。
-
文件描述符表,包含很多指向file结构体的指针。
-
和信号相关的信息。
-
用户id和组id。
-
会话(Session)和进程组。
-
进程可以使用的资源上限(Resource Limit)。
4.进程的状态
- 在三态模型中,进程状态分为三个基本状态,即运行态,就绪态,阻塞态。
- 在五态模型中,进程分为新建态、终止态,运行态,就绪态,阻塞态。
①TASK_RUNNING:进程正在被CPU执行。当一个进程刚被创建时会处于TASK_RUNNABLE,表示己经准备就绪,正等待被调度。
②TASK_INTERRUPTIBLE(可中断):进程正在睡眠(也就是说它被阻塞)等待某些条件的达成。一旦这些条件达成,内核就会把进程状态设置为运行。处于此状态的进程也会因为接收到信号而提前被唤醒,比如给一个TASK_INTERRUPTIBLE状态的进程发送SIGKILL信号,这个进程将先被唤醒(进入TASK_RUNNABLE状态),然后再响应SIGKILL信号而退出(变为TASK_ZOMBIE状态),并不会从TASK_INTERRUPTIBLE状态直接退出。
③TASK_UNINTERRUPTIBLE(不可中断):处于等待中的进程,待资源满足时被唤醒,但不可以由其它进程通过信号或中断唤醒。由于不接受外来的任何信号,因此无法用kill杀掉这些处于该状态的进程。而TASK_UNINTERRUPTIBLE状态存在的意义就在于,内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程,于是原有的流程就被中断了,这可能使某些设备陷入不可控的状态。处于TASK_UNINTERRUPTIBLE状态一般总是非常短暂的,通过ps命令基本上不可能捕捉到。
④TASK_ZOMBIE(僵死):表示进程已经结束了,但是其父进程还没有调用wait4或waitpid()来释放进程描述符。为了父进程能够获知它的消息,子进程的进程描述符仍然被保留着。一旦父进程调用了wait4(),进程描述符就会被释放。
⑤TASK_STOPPED(停止):进程停止执行。当进程接收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等信号的时候。此外,在调试期间接收到任何信号,都会使进程进入这种状态。当接收到SIGCONT信号,会重新回到TASK_RUNNABLE。
5.进程相关函数
//进程号
int main()
{
pid_t pid, ppid, pgid;
pid = getpid();//获取本进程号(PID)
printf("pid = %d\n", pid);
ppid = getppid();//获取调用此函数的进程的父进程号(PPID)
printf("ppid = %d\n", ppid);
pgid = getpgid(pid);//获取进程组号(PGID)
printf("pgid = %d\n", pgid);
return 0;
}
pid_t fork(void);
功能:
用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程。
参数:
无
返回值:
成功:子进程中返回 0,父进程中返回子进程 ID。pid_t,为整型。
失败:返回-1。
失败的两个主要原因是:
1)当前的进程数已经达到了系统规定的上限,这时 errno 的值被设置为 EAGAIN。
2)系统内存不足,这时 errno 的值被设置为 ENOMEM。
Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
功能:
结束调用此函数的进程。
参数:
status:返回给父进程的参数(低 8 位有效),至于这个参数是多少根据需要来填写。
返回值:
无
在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。
父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
wait() 和 waitpid() 函数的功能一样,区别在于,wait() 函数会阻塞,waitpid() 可以设置不阻塞,waitpid() 还可以指定等待哪个子进程结束。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:
等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收该子进程的资源。
参数:
status : 进程退出时的状态信息。
返回值:
成功:已经结束子进程的进程号
失败: -1
宏函数可分为如下三组:
1) WIFEXITED(status)
为非0 → 进程正常结束
WEXITSTATUS(status)
如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
2) WIFSIGNALED(status)
为非0 → 进程异常终止
WTERMSIG(status)
如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
3) WIFSTOPPED(status)
为非0 → 进程处于暂停状态
WSTOPSIG(status)
如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status)
为真 → 进程暂停后已经继续运行
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
功能:
等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。
参数:
pid : 参数 pid 的值有以下几种类型:
pid > 0 等待进程 ID 等于 pid 的子进程。
pid = 0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会等待它。
pid = -1 等待任一子进程,此时 waitpid 和 wait 作用一样。
pid < -1 等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。
status : 进程退出时的状态信息。和 wait() 用法一样。
options : options 提供了一些额外的选项来控制 waitpid()。
0:同 wait(),阻塞父进程,等待子进程退出。
WNOHANG:没有任何已经结束的子进程,则立即返回。
WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(由于涉及到一些跟踪调试方面的知识,加之极少用到)
返回值:
waitpid() 的返回值比 wait() 稍微复杂一些,一共有 3 种情况:
1) 当正常返回的时候,waitpid() 返回收集到的已经回收子进程的进程号;
2) 如果设置了选项 WNOHANG,而调用中 waitpid() 发现没有已退出的子进程可等待,则返回 0;
3) 如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在,如:当 pid 所对应的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid() 就会出错返回,这时 errno 被设置为 ECHILD;
6.孤儿进程和僵尸进程
- 父进程运行结束,但子进程还在运行(未运行结束)的子进程就称为孤儿进程(Orphan Process)。
- 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
7.进程替换函数
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
int execve(const char *filename, char *const argv[], char *const envp[]);