一、理论
1、定义
- 进程是一个具有一定独立功能的程序的一次运行活动,执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。
- 是多任务管理方式
- 进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位,是资源(CPU、内存)分配的最小单元;
- 即CPU和内存的分配以进程为单位。
-
系统进程:用于完成操作系统的各种功能的进程就是系统进程
用户进程:由用户启动的进程就是用户进程
2、命令行
#ps -eo pid,ppid,comm,cmd
- ps查看进程状态
- e所有
- o显示进程标识
- ps -eo 自己进程的id
- ppid,当前进程的父进程的id
- comm当前进程的内容
- cmd 实现当前进程的命令
- 👇
#pstress(进程树)——显示进程的族谱关系 👆
3、进程与线程
-
(1)相同点:
- 进程和线程都有ID/寄存器组、状态和优先权、信息块,创建后都可更改自己的属性,都可与父进程共享资源、都不能直接访问其他无关进程或线程的资源
-
(2)不同点
- 1、定义
- 进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。
- 线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。
- 2、一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。
- 3、线程是一种轻量级的进程,与进程相比,线程给操作系统带来侧创建、维护、和管理的负担要轻,意味着线程的代价或开销比较小。
- 4、线程没有地址空间,线程包含在进程的地址空间中。线程上下文只包含一个堆栈、一个寄存器、一个优先权,线程文本包含在他的进程 的文本片段中,进程拥有的所有资源都属于线程。所有的线程共享进程的内存和资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段, 寄存器的内容,栈段又叫运行时段,用来存放所有局部变量和临时变量。
- 5、父和子进程使用进程间通信机制,同一进程的线程通过读取和写入数据到进程变量来通信。
- 6、进程内的任何线程都被看做是同位体,且处于相同的级别。不管是哪个线程创建了哪一个线程,进程内的任何线程都可以销毁、挂起、恢复和更改其它线程的优先权。线程也要对进程施加控制,进程中任何线程都可以通过销毁主线程来销毁进程,销毁主线程将导致该进程的销毁,对主线程的修改可能影响所有的线程。
- 7、子进程不对任何其他子进程施加控制,进程的线程可以对同一进程的其它线程施加控制。子进程不能对父进程施加控制,进程中所有线程都可以对主线程施加控制。
- 1、定义
4、进程与程序
-
(1)定义
- 程序是放到磁盘的可执行文件。
- 进程是程序执行的实例。
-
(2)进程是动态的,程序使静态的
- 程序是有序代码的集合﹔进程是程序的执行。
- 通常进程不可在计算机之间迁移﹔而程序通常对应着文件、静态和可以复制
-
(3)进程是暂时的,程序使长久的
- 进程是一个状态变化的过程,程序可长久保存
-
(4)进程与程序组成不同
- 进程的组成包括程序、数据和进程控制块(即进程状态信息)
-
(5)进程与程序的对应关系
- 通过多次执行,一个程序可对应多个进程;
- 通过调用关系,一个进程可包括多个程序。
5、进程的生命周期
-
创建
- 每个进程都是有其父进程创建,进程可以创建子进程,子进程又可以创建紫禁城的子进程。
-
运行
- 多个进程可以同时存在,进程间可以通信
-
撤销
- 进程可以被撤销,从而结束一个进程
6、进程的状态(经典三态)
-
(1)执行状态
- 进程正在占用CPU
-
(2)就绪状态(就绪队列)
- 进程已具备一切条件,正在等待分配CPU的处理时间片
-
(3)等待状态(等待队列)
- 进程不能使用CPU,若待事件发生时即可唤醒。
7、Linux进程
Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。也就是说,每个进程都是一个独立的运行单位,拥有各自的权利和责任。其中,各个进程都运行在独立的虚拟地址空间,因此,即使一个进程发生异常,它也不会影响到系统中的其他进程。
Linux中的进程包含3个段,分别为“数据段”、“代码段”和“堆栈段”。
- 用户态:3G
- 内核态:1G
- 代码段:程序代码的数据
- 数据段:全局变量、常数以及动态数据分配的数据空间
- 堆区:可扩展
- 堆栈段:子程序的返回地址、子程序的参数以及程序的局部变量等
8、进程ID
- 进程ID(PID):标识进程的唯一数字
- getpid——当前进程的id
- 父进程的ID (PPID)
- getppid
- 用户ID (UID )——User ID
- 用户组ID(GID)——Group ID
- 有效用户进程(EUID)、
- EGID
9、进程互斥
- 定义
- 进程互斥是指当有若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用(排他),其他要使用该资源的进程必须等待,直到占用该资源者释放了该资源为止。
- 解决方法
- 同步
10.临界资源
操作系统中一次只允许一个进程访问的资源。
11、临界区
进程中访问临界资源的那段程序代码称为临界区为实现对临界资源的互斥访问,应保证诸进程互斥地进入各自的临界区。
12、进程同步(同步≠同时)
- 一组并发进程按一定的顺序执行的过程称为进程间的同步
- 具有同步关系一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件
13、进程调度(低级调度)
- 概念
- 按一定算法,从一组待运行的进程中选出一个来占有CPU运行。
- 调度方式:
- 抢占式/剥夺式
- 非抢占式/非剥夺式
高级调度——作业调度
中级调度——交换调度(虚拟内存、物理内存)
低级调度——进程调度(硬件)
14、调度算法
- 先来先服务调度算法
- 短进程优先调度算法
- 高优先级优先调度算法
- 时间片轮转法
15、死锁
多个进程因竞争资源而形成—种僵局。若无外力作用,这些进程都将永远不能再向前推进。
16、僵尸进程
(1)原因
通常情况下,造成僵尸进程的成因是因为该进程本应该已经执行完毕,但是该进程的父进程却无法完整的将该进程结束掉,而造成该进程一直存在于内存中。通俗一点,僵尸进程就是指子进程先于父进程挂掉 但是父进程并没有正确回收子进程的资源而已。
之所以会出现僵尸进程,因为操作系统在接受到一个信号时,在执行处理函数时,就会屏蔽该信号,所以当有多个子进程同时退出发送信号时,操作系统就收到一个信号,所以就造成僵尸进程的出现。
(2)危害
僵尸进程是1个早已死亡的进程,但在进程表(processs table)中仍占了1个位置(slot)。由于进程表的容量是有限的,所以就占用了内存资源,影响系统性能。
(3)查看僵尸进程
方法一:
ps:将某个时间点的进程运行状态选取下来
ps aux //查看系统所有的进程数据
- -A:所有的进程均显示出来
- -a:不与terminal有关的所有进程
- -u:有效用户相关的进程
- -x:通常与a一起使用,可以列出较完整的信息
- -l:较长、较详细地将该PID的信息列出
如:#ps -l
方法二:
top:动态查看进程的变化
top:参数
- -d:后面接秒数,表示显示整个进程界面更新的秒数,如top -d 5
- -b:以批次的方式执行top
- -n:与-b搭配,意思是需要几次top输出的结果
- -p:指定某个特定的PID进行检测
如:#top -d 5
(4)解决措施——进程等待
17、孤儿进程
- 当一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。
- 父进程是先于子进程结束的,最需要注意的是当父进程退出后,子进程的PPID变为1。
- 在操作系统领域中,孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
- 由此可见,孤儿进程并不会有什么危害,因为有init为它善后,
二、进程控制编程
1、进程创建
(1)fork——子进程的数据空间、堆栈空间都会从父进程得到一个拷贝
- #include <unistd.h>
- #include <sys/types.h>
- pid_t fork(void)
- 功能:创建子进程
- fork的奇妙之处在于它被调用一次,却返回两次,它可能有三种不同的返回值:-1(出错);≥0;
- 在pid=fork()之前,只有一个进程在执行,但在这条语句执行之后,就变成两个进程在执行了,这两个进程的共享代码段,将要执行的下一条语句都是if(pid==0). 两个进程中,原来就存在的那个进程被称作“父进程”,新出现的那个进程被称作“子进程”,父子进程的区别在于进程标识符(PID)不同.
-
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include<stdlib.h> int main() { pid_t pid; /*此时仅有一个进程*/ int count=0; pid=fork(); /*此时已经有两个进程在同时运行*/ if(pid<0) { printf("error in fork!"); exit(-1); } else if(pid==0) { count++; printf("count=%d\n",count); printf("I am the child process, ID is %d\n",getpid()); //子进程 } else //pid>0 { count++; printf("count=%d\n",count); printf("I am the parent process,ID is %d\n",getpid()); //父进程 } return 0; }
-
输出: count = 1 count = 1
-
进程创建-思考运行结果显示:
-
子进程的数据空间、堆栈空间都会从父进程得到一个拷贝,而不是共享。在子进程中对count进行加1的操作,并没有影响到父进程中的count值,父进程中的count值仍然为0
-
(2)vfolk——子进程的数据空间、堆栈空间都会从父进程得到一个共享
- #include <sys/types.h>
- #include <unistd.h>
- pid_t vfork(void)
- 功能:创建子进程
-
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid; /*此时仅有一个进程*/ int count=0; pid=vfork(); /*此时已经有两个进程在同时运行*/ if(pid<0) { printf("error in fork!"); exit(-1); } else if(pid==0) { count++; printf("count=%d\n",count); printf("I am the child process, ID is %d\n",getpid()); //子进程 _exit(0); //系统调用unistd.h } else //pid>0 { count++; printf("count=%d\n",count); printf("I am the parent process,ID is %d\n",getpid()); //父进程 } // exit (0); //stdlib.h return 0; }
(3)“fork” PK “vfork”
- 区别1:
- fork:子进程拷贝父进程的数据段
- vfork:子进程与父进程共享数据段
- 区别2:
- fork:父、子进程的执行次序不确定
- vfork:子进程先运行,父进程后运行(子先父后)
2、进程退出
-
exit()函数用于在程序运行的过程中随时结束程序,exit的参数state是返回给操作系统,返回0表示程序正常结束,非0表示程序非正常结束。
-
_exit(0)不清空文件缓冲区
-
exit(0)清空文件缓冲区
-
3、exec函数族(子进程执行新任务)
- exec用被执行的程序替换调用它的程序。
- 区别
- fork创建一个新的进程,产生一个新的PID。
- exec启动一个新程序,替换原有的进程,因此进程的PID不会改变
- #include<unistd.h>
-
int execl(const char * path,const char * arg1, ....)
- 参数
- path:被执行程序名(含完整路径)。
- arg1 – argn: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。
- #include <unistd.h> int main() { execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,NULL); }
- 参数
- #include<unistd.h>
-
int execv (const char * path, char * const argv[ ])
- 参数
- path:被执行程序名(含完整路径)。
- argv[]: 被执行程序所需的命令行参数数组。
- #include <unistd.h> int main() { char * argv[ ]={“ls”,”-al”,”/etc/passwd”,(char*)0}; execv(“/bin/ls”,argv); }
- #include <stdlib.h>
-
int system( const char* string )
-
功能
-
调用fork产生子进程,由子进程来调用/bin/sh -c string来执行参数string所代表的命令
-
-
#include <stdlib.h> int main() { system(“ls -al /etc/passwd”); }
4、进程等待——处理僵尸进程 - #include <sys/types.h>
- #include <sys/wait.h>
- pid_t wait (int * status)
- int * status为输出进程状态,多数情况下为NULL
- 功能: 阻塞该进程,直到其某个子进程退出。
- 缺点:只能父进程等待(第一个结束的)子进程
一次处理函数wait多个子进程,函数返回值正常返回子进程的PID,如果设置了第三个参数,即WNOHANG表示非阻塞式等待时,如果返回值为0则表示没有需要回收的子进程了,基于这一点,我们可以在处理函数中循环调用waitpid函数。
代码如下:
- pid_t waitpid(pid_t pid,int *status,int options) //可指定等哪个
函数说明:waitpid ()会暂停目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status返回,而子进程的进程识别码也会--块返回,如果不在意结束状态值,则参数status可以设成NULL。参数pid 为欲等待的子进程识别码、其他数值意义如下:
参数 pid 为欲等待的子进程识别码,其他数值意义如下:
- pid<-1 等待进程组识别码为 pid 绝对值的任何子进程。
- pid=-1 等待任何子进程,相当于 wait()。
- pid=0 等待进程组识别码与目前进程相同的任何子进程。
- pid>0 等待任何子进程识别码为 pid 的子进程。
参数 option 可以为 0 或下面的 OR 组合:
- WNOHANG ———如果没有任何已经结束的子进程则马上返回, 不予以等待。
- WUNTRACED ——如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
void Myhandler(sigset_t signal)
{
pid_t id;
while((id = waitpid(-1,NULL,WNOHANG) > 0)){
printf("child wait success:%d\n",id);
}
printf("child is quit!\n");
}
int main()
{
signal(SIGCHLD,Myhandler);
pid_t cid;
int i = 0;
for(;i < 20;++i)
{
cid = fork();
if(cid == 0)
exit(0);
}
if(cid > 0)
{
while(1)
{
printf("father doing some thing!\n");
sleep(1);
}
}
else if(cid == 0)
{
sleep(3);
}
return 0;
}