进程管理
进程的概念
- 程序是一个包含可以执行代码的文件,是一静态的文件。
- 进程是一个开始执行但还没有结束的程序的实例,就是可执行文件的具体实现,是一个动态的概念。
- Linux系统上所有运行的东西都可以称为一个进程。
- 在一个CPU上,可以存在多个进程;在同一时间内,一个CPU只能有一个进程工作。操作系统通过一定的调度算法管理算有进程。
- Linux系统至少有一个进程。一个程序可以对应多个进程,一个进程只能对应一个程序。
- 进程这个概念是针对系统而不是针对用户的
- 用户面对的概念是程序:当用户敲入命令,执行一个程序; 对系统而言,它将启动一个进程。 和程序不同的是,系统不
- 仅仅是在运行一个程序,系统为了管理、控制、协调整个计算机,有可能要运行很多个“程序”—这些程序有些在用户看来可能是“同时”在运行着,但实际上系统在后台进行着相应的调度
- 多进程编程:为了更高效的使用CPU 在同一个时间做多个任务
进程的基本状态三种:运行态、就绪态和阻塞态(或等待态)。
进程号pid
- 每个进程有唯一的pid,可以用getpid()函数获取
pid_t getpid(void);
pid_t getppid(void);
说明:
getpid()返回调用该系统调用的进程的pid号;
getppid()返回调用该系统调用的进程的父进程pid号。
#include <stdio.h>
int main()
{
printf("Current process %d\n",getpid());
printf("Parent process %d\n",getppid());
return 0;
}
进程控制-初识fork
- fork函数:产生子进程
- fork:分叉
- 子进程完全复制父进程的资源
- 子进程的执行独立于父进程
- 表现形式:调用一次,返回2次
- 在子进程里:返回0
- 父进程里:返回子进程的pid
- 头文件: unistd.h
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(){
int p_id;
p_id=fork( );
if (p_id==0)
printf("child process\n");
else
printf("parent process\n");
}
- 原有的进程称为父进程,新生的进程称为子进程
进程的创建过程
- fork返回值小于0表示创建进程失败
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(){
int p_id;
p_id=fork( );
if(p_id < 0) {
printf("create process error\n");
exit(-1);
}
if (p_id==0)
printf("child process\n");
else{
printf("parent process\n");
wait();
}
}
最简单的进程同步:wait
-
fork产生子进程后,父子进程在系统调度下执行
- 父子进程的调度顺序是随机的
-
如果希望父进程在子进程结束后再结束,可以使用wait函数:
pid_t wait (int * status); (头文件: wait.h)
- wait函数会“暂停”目前进程的执行,直到有信号来到或子进程结束
- 如果在调用
wait()
时子进程已经结束,则wait()
会“立即”返回子进程结束状态值,如果有错误发生则返回返回值-1
。失败原因存于errno
中。 - 子进程的结束状态值(如exit函数中的值)会存放在参数
status
中, 而wait
函数的返回值是子进程的进程号 waitpid:pid_t waitpid(pid_t pid,int * status,int options);
- 等待指定的子进程结束
-
wait
:父进程等待“任一”子进程结束 -
waitpid
:等待指定的子进程结束 -
如何等待所有子进程结束?
if ( wait() >0 )
说明有子进程结束,判断一次while ( wait() > 0 ) ;
僵尸进程和孤儿进程
-
僵尸进程:一个子进程在其父进程还没有调用
wait()
或waitpid()
的情况下退出。这个子进程就是僵尸进程。 -
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。
- 孤儿进程将被
init
进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
- 孤儿进程将被
-
僵尸进程将会导致资源浪费
- 进程结束后仍有一些信息,包括进程号
the process ID
,退出状态the termination status of the
process
,运行时间the amount of CPU time taken by the process
等
- 进程结束后仍有一些信息,包括进程号
僵尸进程
多个子进程
分析程序有多少个输出?
int main(){
int p_id;
p_id=fork( );
if (p_id==0)
printf("child process 1\n");
else
{
wait();
printf("parent process 1\n");
}
p_id=fork( );
if (p_id==0)
printf("child process 2\n");
else
{
wait();
printf("parent process 2\n");
}
}
进程链
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int i;
pid_t child_pid;
for(i=1;i<3;i++)
{
child_pid = fork();
if( child_pid==0 )
{ printf( "do something \n");
return 0;
}
else
{ wait();
}
}
return 0;
}
- 程序的输出是怎样的?
- 运行后,用多进程去解释你看到的输出 程序中红色的return 0 和wait有何意义?
- return 0使得子进程在执行完自身代码后直接返回(类似函数),不再执行if else之后的代码,即不再参与fork函数的第二次调用。
多进程-执行其他程序
-
fork创建子进程后,子进程执行的是和父进程相同的程序(执行不同的代码分支),子进程通常调用exec函数系列来执行另一个程序。
-
当进程调用exec函数系列时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
-
进程的PID不会改变,依然是fork函数调用时产生的PID。
-
exec函数族
execlp(可执行文件名, 参数0,参数1,......,NULL)
函数不会返回,除非出错, 执行失败则直接返回-1,失败原因存于errno 中- 系统会在path路径中去找可执行文件
- 参数从参数0开始(类似于main参数),程序执行的参数从参数1开始, 参数0可以为空,或是可执行文件名
- exec其他函数
execl,execlp,execle,execv,execve,execvp
等
exec函数族参数
#include <unistd.h>
extern char **environ;
int execve(const char *filename, char *const argv[], char *const envp[]);
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
fork产生进程的缺陷
- 由于在使用fork时,内核会将父进程拷贝一份给子进程,但是这样的做法相当浪费时间,因为大多数的情形都是程序在调用fork后就立即调用exec,这样刚拷贝来的进程区域又立即被新的代码和数据覆盖掉
vfork特点
- 创建时,父子共享一块空间
- 父进程睡眠,子进程运行
- 父进程醒来时机:
- 子进程运行结束
- 子进程执行其它任务
fork:父子进程拷贝数据段;父子进程的执行次序不确定 vfork:父子进程共享数据段;子进程先运行,父进程后运行
vfork使用
头文件:#include<unistd.h>
#include <sys/types.h>
格式: pid_t vfork();
返回值:
0:子进程
子进程ID(大于0的整数):父进程
-1:出错
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
void main()
{ pid_t pid;
int x,y;
x=20,y=30;
pid=vfork();
if(pid<0)
{ printf("oh,my god,create a baby failed!\n");
exit(1);
}else if(pid==0)
{ x=40,y=50;
sleep(1);
printf("child:x=%d,y=%d\n",x,y);
exit(0);
}
printf("parent:x=%d,y=%d\n",x,y);
}
运行结果:
child:x=40,y=50
parent:x=40,y=50
#include <unistd.h>
#include <stdio.h>
int main(void)
{
pid_t pid;
int count=0;
pid = fork();
count++;
printf( “count = %d\n", count );
return 0;
}
输出:
count = 1
count = 1
子进程的数据空间、堆栈空间都会从父进程得到一个拷贝,而非共享。
#include <unistd.h>
#include <stdio.h>
int main(void)
{
pid_t pid;
int count=0;
pid = vfork();
count++;
printf( “count = %d\n", count );
return 0;
}
输出:
count = 1
count = 2
共享!!
非拷贝
进程终止
- 在Linux环境中,一个进程的结束,可以采用调用函数 或者 接收到某个特定的信号而结束。
- 调用函数结束进程
exit函数
函数原型
void exit(int status);
作用
用来终止正在运行的进程,使其进入僵死状态,等待父进程进行善后处理,该调用会尽可能释放当前进程占用的资源、清空缓冲区。将关闭所有被该文件打开的文件描述符
status
是返回给父进程的一个整数。
#include<stdio.h>
#include<stdlib.h>
main()
{
printf("This process will exit!\n");
exit(0);
printf("Never be displayed!\n");
}
_exit函数
- 函数原型
void _exit(intstatus)
- 作用
_exit()
函数也可用于结束一个进程,该调用不会刷新输入输出缓冲区,因此进程结束前必须自己刷新缓冲区,或者改用exit()系统调用。
sleep函数调用
- 系统调用sleep用来使进程主动进入睡眠状态,该函数的一般形式是:
sleep(秒数);
- 执行该系统调用后,进程将进入睡眠状态,直到指定的秒数已到。正常情况下,该调用的返回值为0,若是因为被信号所唤醒,则返回值为原始秒数减去已睡眠秒数的差。
ps
进程查看命令
ps [选项]
-e:显示所有进程。
-f:全格式。
-h:不显示标题。
-l:长格式
-w:宽输出
a:显示终端上的所有进程,包括其他用户的进程
r:只显示正在运行的进程
x:显示没有控制终端的进程
u:使用用户格式输出
进程管理相关命令
kill
当用户需要中断一个前台进程的时候,通常是使用<Ctrl+c>组合键;
对于一个后台进程须求助于kill命令
kill命令的语法格式很简单,大致有以下两种方式:
kill [-s 信号 | -p ] [ -a ] 进程号
kill -l [信号]
top
显示系统当前的进程和其他状况
top [-dqsiupSc] [-d count] [-s time] [-u username]
常用选项含义如下:
d
:指定每两次屏幕信息刷新之间的时间间隔。
q
:表示没有任何延迟地进行刷新。
s
:表示安全模式下运行。
top
显示系统当前的进程和其他状况
top [-dqsiupSc] [-d count] [-s time] [-u username]
常用选项含义如下:
d
:指定每两次屏幕信息刷新之间的时间间隔。
q
:表示没有任何延迟地进行刷新。
s
:表示安全模式下运行。
wait
wait命令将实现对一个进程的等待。命令格式为:
wait [n]
等待进程号为n的一个进程的完成并将报告进程的终止状态。没有参数,则将等待所有后台进程的完成并返回代码0。
示例:等待进程号为13199的进程结束。
sleep
sleep命令,将进程的执行挂起一段时间。语法格式为:
sleep time
即使得shell挂起time秒后,再继续执行。