基本概念
程序:程序是静止的,是磁盘上的一个文件。
课本知识:程序的一个执行实例,正在执行的程序等(动态的)。
内核观点:担当分配系统资源(CPU时间、内存)的实体。
程序一旦被操作系统加载到内存中,那么程序就变成了进程。
进程信息放在一个叫进程控制块(PCB)的数据结构中,可以理解为进程属性的集合。
Linux中描述进程的结构体叫做task_struct。
task_struct是Linux内核的一种数据结构,会被装载到RAM(内存)里并且包含着进程的信息
在进程执行时,任意给定一个时间,进程都可以唯一的被表征为以下元素:
标识符:跟这个进程相关的唯一标识符,用来区分其他进程。
状态:如果进程正在执行,那么进程处于运行状态。
优先级:相对于其他进程的优先级。
程序计数器:程序中即将被执行的下一条指令的地址。
内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享内存块的指针。
上下文数据:程序执行时处理器的寄存器中的数据。
I/O状态信息:包括显示的I/O请求、分配给进程的I/O设备和被进程使用的文件列表。
记帐信息:可能包括处理器时间总和、使用的时钟数总和、时间限制、记账号等。
因此,可以说进程是由程序代码和相关数据还有进程控制块组成。对于一个单处理器计算机,在任何时间最多只有一个进程在运行,正在运行的这个进程状态为运行态。
进程的创建和终止
- 进程的创建:
当一个新进程添加到那些正在被管理的进程集合中去时,操作系统需要建立用于管理该进程的数据结构,并在内存中给它分配地址空间,这些行为构成了一个新进程的创建过程。通常会有以下4个事件导致创建一个新进程:
- 进程的终止:
任何一个计算机都必须为进程提供表示其完成的方法,批量处理作业中应该包含一个Halt指令或用于终止操作系统显示服务调用来终止。在前一种情况下,Halt指令将产生一个中断,警告操作系统一个进程已经完成。对交互式应用程序,用户的行为将指出何时进程完成。在有些操作系统中,进程可以被创建它的进程终止,或当父进程终止而终止。以下是进程终止的几种情况:
查看进程
PID:pid_t getpid(void);得到进程的PID
PID是程序被操作系统加载到内存成为进程后动态分配的资源。
每次程序执行的时候,操作系统都会重新加载,PID在每次加载的时候都是不同的。PPID:pid_t getppid(void);得到进程的PPID
PPID是程序的父进程号。一个进程创建的另一个新进程称为子进程。相反地,创建子进程的进程称为父进程。对于一个普通的用户进程,它的父进程就是执行它的那个Shell,对于Linux而言,Shell就是bash。
所有进程的祖先为init进程。
创建一个进程:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int mian(){
while(1){
sleep(1);
}
return 0;
}
通过系统调用获取进程标识符
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int mian(){
pid_t pid = getpid();
printf("pid:%d\n",pid);
pid_t ppid = getppid();
printf("ppid:%d\n",ppid);
while(1){
sleep(1);
}
return 0;
}
ps axj | grep ./a.out
通过系统调用创建进程
fork特点:
一次调用有两个返回值,父进程返回子进程PID,子进程返回0。
父进程和子进程都从fork执行结束后的位置继续执行。
子进程以父进程为模板(PCB,数据和代码)。写时拷贝。
父子进程执行的先后顺序不确定,取决于操作系统调度器。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int mian(){
int ret = fork();
printf("pid: %d, ppid: %d, ret: %d\n", getpid(), getppid(), ret);
while(1){
sleep(1);
}
return 0;
}
使用if进行分流:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int mian(){
int ret = fork();
if(ret < 0){
perror("fork");
return 1;
}
else if(ret == 0){
printf("I am child: pid: %d, ppid: %d, ret: %d\n", getpid(), getppid(), ret);
}
else{
printf("I am father: pid: %d, ppid: %d, ret: %d\n", getpid(), getppid(), ret);
}
while(1){
sleep(1);
}
return 0;
}
进程状态
R(就绪):表明进程要么在运行中,要么在运行队列中
S(挂起):进程在等待事件完成(可中断睡眠)
D(深度睡眠):进程通常会等待I/O结束(不可中断睡眠)
T(暂停):可以通过发送SIGSTOP信号停止进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
t(跟踪)
X(死亡):只是一个返回状态。
z(僵尸状态):它的父进程尚未调用wait函数
停止9037号进程:
恢复:
僵尸进程
当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。
僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
int ret = fork();
if(ret < 0){
perror("fork");
return 1;
}
else if(ret > 0){
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}
else{
printf("child[%d] is begin z...\n",getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
僵尸进程会占用系统资源,如果很多,则会严重影响服务器的性能
若一直不回收,有可能造成内存泄漏
孤儿进程
- 一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int mian(){
int ret = fork();
if(ret < 0){
perror("fork");
return 1;
}
else if(ret == 0){
printf("I am child: pid: %d, ppid: %d, ret: %d\n", getpid(), getppid(), ret);
sleep(10);
}
else{
printf("I am father: pid: %d, ppid: %d, ret: %d\n", getpid(), getppid(), ret);
sleep(3);
exit(0);
}
return 0;
}