进程
一、什么是进程?
1、从用户角度:进程是程序的一次动态执行过程。
2、从操作系统角度:进程是操作系统分配内存,时间片等资源的基本单位
3、进程是操作体统可分配资源的最小单位,且每个进程都有自己独立的地址空间和执行状态
二、进程数据结构
1、PCB:用于描述进程状况以及控制进程运行的全部信息
2、数据段:程序运行时需要的数据
3、代码段:编译后形成的一些指令
4、堆栈段:程序运行时动态分配的一些内存
三、进程与程序的异同
1、进程是动态的,而程序是静态的;
2、一个进程只能对应一个程序,而一个程序可以对应多个进程;
四、进程的状态
1)、进程的三种基本状态
a、就绪(Ready)状态
当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,
这时的进程状态称为就绪状态。
b、执行(Running)状态
当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状
态。
c、阻塞(Blocked)状态
正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻
塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、
等待信件(信号)等。
2)、Linux中进程状态
a、运行状态(TASK_RUNNING):是运行态和就绪态的合并,表示进程正在运行
或准备运行,Linux 中使用TASK_RUNNING 宏表示此状态
b、可中断睡眠状态(浅度睡眠)(TASK_INTERRUPTIBLE):进程正在睡眠(被阻
塞),等待资源到来是唤醒,也可以通过其他进程信号或时钟中断唤醒,进入运行
队列。Linux 使用TASK_INTERRUPTIBLE 宏表示此状态。
c、不可中断睡眠状态(深度睡眠状态)(TASK_UNINTERRUPTIBLE):
其和浅度睡眠基本类似,但有一点就是不可被其他进程信号或时钟中断唤醒。
Linux 使用TASK_UNINTERRUPTIBLE 宏表示此状态。
d、暂停状态(TASK_STOPPED):进程暂停执行接受某种处理。如正在接受调试的
进程处于这种状态,Linux 使用TASK_STOPPED 宏表示此状态。
e、僵死状态(TASK_ZOMBIE):进程已经结束但未释放PCB,Linux 使用
TASK_ZOMBIE 宏表示此状态
五、相关函数
1)、子进程的创建
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回:
成功子:进程中为0,父进程中为子进程ID (可类比链表)
失败:-1
详解:
1、fork出的子进程继承父进程的整个地址空间,如图:
由此可见,fork出的子进程将得到父进程的一个拷贝,因此在子进程上修改数据不会影响到父
进程。实际上由于fork之后经常跟随着 exec。作为替代,现在的实现是使用了在写时复制(
Copy-On-Write, COW)的技术。这些区域由父、子进程共享,而且内核将它们的存取许可权
改变为只读的。如果有进程试图修改这些区域,则内核为有关部分,典型的是虚存系统中
的“页”,做一个拷贝。
2、fork之后,父子进程都将从fork之后的代码开始执行
3、fork之后,父子进程共享文件,也就是说任一进程修改文件都会是文件表中的信息被修改。
4、另一个进程创建函数
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(); ---不推荐使用
vfork()同样是创建子进程,但区别在于,vfork()保证子进程先运行,在它调用exec或exit之后
父进程才被调度。vfork()设计出来的目的是exec一个新程序,因此它并不将父进程的地址空间
完全复制到子进程。(vfork 是在fork 没有实现COW时,为避免exec而造成地址空间浪费而
设计出来,早先的fork总是会对父进程的地址空间进行拷贝)
2)、进程的终止
1、 正常终止:
a) 在main函数内执行return语句。(这等效于调用exit)
b) 调用exit函数。此函数由ANSI C定义,其操作包括调用各终止处理程序(终止处
理程序 在调用atexit函数时登录),然后关闭所有标准I / O流等。
c) 调用_exit系统调用函数。
2、 异常终止:
a) 调用abort。它产生SIGABRT信号,所以是下一种异常终止的一种特例。
b) 当进程接收到某个信号时。
3、相关函数
#include <stdlib.h>
void exit(int status);
int atexit(void (*function)(void)); //1、用于注册终止处理函数,由exit()调用;
//2、子进程继承父进程注册的处理函数;
//3、atexit注册的函数中有一个没有正常返回被
//kill,则后续的注册函数都不会被执行。
----------------------------------------------------------------------
#include <unistd.h>
void _exit(int status);
exit和_exit的区别 :
从图中可直观的看到exit在结束调用它的进程之前会先执行终止处理函数,
再执行标准I/O清除,但最终都会执行_exit来进入内核,清除其使用的内存
空间,并销毁其再内核中的各种数据结构。
3)、wait函数(父进程查询子进程的退出)
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
返回
成功:进程ID
失败:-1
1、僵尸进程
在介绍wait和waitpid的功能之前先来看看僵尸进程的含义:
当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常 地结束运行,或者父进程调用了wait才告终止。
子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。
进程表中代表子进程的数据项是不会立刻释放的,虽然不再活跃了,可子进程还停留在系统里,因为它的退出码还需要保存起来以备父进程中后续的wait调用使用。它将称为一个“僵尸进程”,用ps查看会以“Z”表示。
避免方式:
a、调用wait或者waitpid函数查询子进程退出状态,此方法父进程会被挂起。
b、如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);
表示父进程忽略SIGCHLD信号(子进程退出时,内核会向父进程发送SIGCHLD),该
信号是子进程退出的时候向父进程发送的。此处的SIG_IGN尽管也是忽略,但与
SIGCHLD的默认忽略SIG_DEF不同,SIG_IGN会将僵尸进程转交给init进程处理,从而
避免僵尸进程。
2、函数详解
参数:statloc:状态指针,指向的内存表示终止进程的终止状态,可设置为NULL
pid:指定进程 ---- a、pid == -1 等待任一子进程。于是在这一功能方面
waitpid与wait等效。
b、pid > 0 等待其进程I D与p i d相等的子进程。
c、pid == 0 等待其组I D等于调用进程的组I D的任一子
进程。
d、pid < -1 等待其组I D等于p i d的绝对值的任一子进
程。
options: WNOHANG ---- 不阻塞,此时返回0
WUNTANCED ---- 阻塞
两个函数的区别:
a、在一个子进程终止前, wait 使其调用者阻塞,而 waitpid 有一选择项,可使调用
者不阻塞。
b、waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的
进程。
4)、exec系列函数
#include <unistd.h>
extern char **environ;
int execle(const char *pathname, const char *arg, ..., char * const envp[]);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execl(const char *pathname, const char *arg, ...);
int execv(const char *pathname, char *const argv[]);
int execlp(const char *pathname, const char *arg, ...);
int execvp(const char *pathname, char *const argv[]);
返回:
成功:不返回
失败:-1
参数:
pathname:需要执行程序的地址
arg:参数指针
argv:参数指针数组
---这里的参数即是传递给新程序main的argv参数
envp:环境表
此图可直观的反应六个函数的关系。
从垂直角度来看:"execl"类函数与"execv"类函数的区别在于第二个参数,l表示表(list,一系列
参数,但均以NULL结尾),v表示矢量(vector, 参数在指针数组中)
从纵向角度来看:execvp可使用相对路径(但必须是PATH中有的地址)-->execv只能使用绝
对路径 --> execve多了一个环境表environ且只能使用绝对路径
exec函数的作用:将子进程完全替换为新的程序,用于实现子进程执行另一个程序。