Linux
系统是多任务操作系统,可同时进行多个程序完成多项工作。
进程是处于活动状态的程序,在操作系统的管理下,所有进程共享计算机中的硬件资源。进程作为系统运行时的基本逻辑成员,不仅作为独立个体运行在系统上,而且还将相互竞争系统资源。
程序是一个包含可以执行代码的文件,是一个静态的文件。
进程的概念:
进程是一个开始执行但还没有结束的程序的实例,就是可执行文件的具体实现,是一个动态的概念。
Linux
系统上所有运行的东西都可以称为一个进程。
当程序被系统调用到内存以后,系统会给程序分配一定的资源(内存、设备等),然后进行一系列的复杂操作,使程序变成进程以供系统调用。
系统给每一个进程分配一个
ID
号以便于识别。
在一个
CPU
上,可以存在多个进程;在同一时间内,一个
CPU
只能有一个进程工作。操作系统通过一定的调度算法管理算有进程。
Linux
系统至少有一个进程。一个程序可以对应多个进程,一个进程只能对应一个程序。
对操作系统来说,进程是程序的一次执行过程和资源分配的基本单位
。
一个进程不仅包含了正在运行的代码,也包括了运行代码所需要的资源(包括用户用到的资源和操作系统需要用的资源)。操作系统通过
PCB
(进程控制块 )的数据结构管理进程。
进程的分类:
进程具有运行、阻塞和就绪
三种基本状态
。
进程大致上来讲可分为两大类:
系统进程与用户进程
(守护进程)
。
进程的基本属性:
进程号(
PID)
:操作系统通过该号标示一个
用户进程。
父进程号(PPID):除了init进程外,所有的进程都是通过init进程创建的。进程又可以创建其他进程,形成一个倒过来的树形结构,每个进程都有自己的父进程。
进程组号(
PGID
):操作系统允许对进程分组,不同的进程通过进程组号标示。
真实用户号(
UID
):用户唯一的标识号,用于标识一个用户。
真实组号(
GID
):用户组的唯一标识号,用于标识一个用户组。
进程管理相关命令:
1.ps
¨
进程查看命令
¨
语法格式如下:
¨
ps
[选项]
¨
常用选项含义如下:
-e
:
显示所有进程。
-f
:全格式。
-h:
不显示标题。
-l:
长格式
-w:
宽输出
a:
显示终端上的所有进程,包括其他用户的进程
r:
只显示正在运行的进程
x:
显示没有控制终端的进程
u:
使用用户格式输出
2.top
¨
显示系统当前的进程和其他状况
¨
命令语法格式如下:
¨
top [-
dqsiupSc
] [-dcount] [-s time] [-u username]
¨
常用选项含义如下:
d
:
指定每两次屏幕信息刷新之间的时间间隔。
q
:
表示没有任何延迟地进行刷新。
s
:
表示安全模式下运行。
3.
renice
¨
renice
命令允许用户修改一个正在运行进程的优先权。利用
renice
命令可以在命令执行时调整其优先权。其命令语法格式如下:
¨
renice
-number PID
¨
其中,参数
number
表示优先级别号。示例:
4. wait
¨
wait
命令将实现对一个进程的等待。命令格式为:
¨
wait
[n]
¨
等待进程号为
n
的一个进程的完成并将报告进程的终止状态。没有参数,则将等待所有后台进程的完成并返回代码
0
。
¨
示例:等待进程号为
13199
的进程结束。
6. kill
¨
当用户需要中断一个前台进程的时候,通常是使用
<
Ctrl+c
>
组合键;
¨
对于一个后台进程须求助于
kill
命令
¨
kill
命令的语法格式很简单,大致有以下两种方式:
¨
kill
[-s 信号| -p ] [ -a ] 进程号
¨
kill
-l [信号]
进程状态和状态转换:
进程在生存周期中呈现出各种状态及状态的转换,这些信息反映了进程获取系统资源的情况。Linux系统的进程状态模型如表所示。
(1).子进程被Linux内核调入CPU执行的过程:
进程的生命周期包括从创建到退出的全部状态转化,它的生命周期里并不一定要经历所有的状态。父进程创建子进程,子进程被
Linux
内核调入
CPU
执行的过程可用跨职能流程图来反映,如后图所示。
最初,父进程通过
fork
系统调用创建子进程,子进程被创建后,处于创建状态。
Linux
内核为子进程配置数据结构,如果内存空间足够,子进程在内核中就绪,否则在
Swap
分区就绪。这时子进程处于就绪状态,等待
Linux
内核调度。Linux
内核会为子进程分配
CPU
时钟周期,在合适的时间将子进程调度上
CPU
运行,这时子进程处于内核状态,子进程开始运行。被分配的
CPU
时钟周期结束时,
Linux
内核再次调度子进程,将子进程调出
CPU
,子进程进入用户状态。待子进程被分配的下一个
CPU
时钟周期到来时,
Linux
内核又将子进程调度到
CPU
运行,使子进程进入内核状态。如果有其它的进程获得更高优先级,子进程的时钟周期可能会被抢占,这时又会回到用户状态。
(2).子进程进入睡眠状态
子进程在运行时,如果请求的资源得不到满足将进入睡眠状态,睡眠状态的子进程被从内存调换到
Swap
分区。被请求的资源可能是一个文件,也可能是打印机等硬件设备。如果该资源被释放,子进程将被调入内存,继续以系统状态执行,如图所示。
(3).子进程结束
子进程可以通过
exit
系统调用结束,这时子进程将进入到僵死状态,生命周期结束,如图所示。子进程在内核中的数据结构又被称为上下文。上下文包括
3
个部分,用户级上下文是子进程用户空间的内容,寄存器上下文是子进程运行时装入
CPU
寄存器的内容,系统级上下文是子进程在
Linux
内核中的数据结构。
子进程切换时,CPU收到一个软中断,这时上下文将被保存起来,称之为保存现场。子进程再次运行时,上下文被还原到相关位置,称之为还原现场。整个过程称为上下文切换,保存上下文的数据空间称为u区,是Linux内核为进程分配的存储空间。
内核在以下情况会进行上下文切换操作:
子进程进入睡眠状态时。
子进程时钟周期结束,被转为用户状态时。
子进程再次被调度上
CPU
运行,转为系统状态时。
子进程僵死时。
进程的创建:
在Linux系统中,用户创建子进程的唯一方法就是使用fork系统调用。fork系统调用的流程如图所示。
1.fork创建进程并系统调用
格式:
#include <unistd.h>
pid_t fork(void);
功能:创建子进程
fork的奇妙之处在于它被调用一次,却返回两次,它可能有三种不同的返回值:
(1).在父进程中,fork返回新创建的子进程的PID;
(2).在子进程中,fork返回0;
(3).如果出现错误,fork返回一个负值
Linux系统通过fork()系统调用创建一个进程,fork()函数定义如下:
#include <sys/
types.h
>
#include <unistd.h>
pid_t fork(void);
fork( )
函数的作用是创建一个新的进程。在应用程序调用
fork( )
函数后,会创建一个新的进程,称为子进程,原来的进程叫做父进程。从此,运行的已经是两个进程了。子进程和父进程都可以得到
fork
函数的返回值。子进程中,
fork
返回
0
;父进程中,
fork
返回子进程的进程号。若创建失败,返回
-1。
例程如下:
#include <sys/types.h>
#include <unistd.h>
main()
{
pid_t pid;
/*此时仅有一个进程*/
pid=fork();
/*此时已经有两个进程在同时运行*/
if(pid<0)
printf("error in fork!");
else if(pid==0)
printf("I am the child process, ID is %d\n",getpid());
else
printf("I am the parent process,ID is %d\n",getpid());
}
在pid=fork()之前,只有一个进程在执行,但在这条语句执行之后,就变成两个进程在执行了,这两个进程的共享代码段,将要执行的下一条语句都是if(pid==0)。两个进程中,原来就存在的那个进程被称作“父进程”,新出现的那个进程被称作“子进程”,父子进程的区别在于进程标识符(PID)不同。
2.vfork创建进程
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void)
功能:创建子进程。
区别:
(1). fork:子进程拷贝父进程的数据段
vfork:子进程与父进程共享数据段
(2). fork:父、子进程的执行次序不确定
vfork:子进程先运行,父进程后运行
例程如下:
# 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;
}
3.exec函数
exec
用被执行的程序替换调用它的程序。
区别:
fork创建一个新的进程,产生一个新的PID。
exec启动一个新程序,替换原有的进程,因此进程的PID不会改变。
格式:
#include <unistd.h>
extern char **environ;
intexecve(const char*filename, char *const argv[], char *const envp[]);
intexecl(const char *path,const char *arg,...);
intexeclp(const char *file,const char *arg,...);
intexecle(const char *path,const char *arg,..., char * const envp[]);
intexecv(const char *path,char *const argv[]);
intexecvp(const char *file,char *const argv[]);
说明:
上面统称为
exec
函数系列,只有
execve
是真正的系统调用,其它五个函数最终都调用
execve
。
用
fork
创建子进程后,子进程执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用
exec
函数系列来执行另一个程序。当进程调用
exec
函数系列时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
参数path是被执行程序的完整路径名;argv和envp是传给被执行程序的命令行参数和环境变量;file是文件名,有相应函数自动到环境变量PATH给定的目录中寻找。
例程如下:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
/*判断入参有没有传入文件名*/
if(argc<2)
{
perror("you haven,t input the filename,please try again!\n");
exit(EXIT_FAILURE);
}
/*调用execl函数,用可执行程序file_creat替换本进程*/
if(execl("./file_creat","file_creat",argv[1],NULL)<0)
perror("execl error!");
}
进程的基本操作:
进程的基本操作包括
fork
调用、
exec
调用、
exit
调用、
wait
调用和
sleep
调用,相关函数被定义在系统调用库
unistd.h
中。
(
1)
.进程终止系统调用(exit函数)
格式:
#include <stdlib.h>
void exit(int status);
说明:
exit()自我终止当前进程,使其进入僵死状态,等待父进程进行善后处理,该调用会尽可能释放当前进程占用的资源。status是返回给父进程的一个整数。
例程:
#include<stdio.h>
#include<stdlib.h>
main()
{
printf("This process will exit!\n");
exit(0);
printf("Never be displayed!\n");
}
系统调用
exit
的功能是终止发出调用的进程,它包含两个函数,分别是
_exit( )
函数和
exit( )
函数。它们的一般形式如下:
void _exit(
int
status);
void exit(
int
status);
系统调用_exit()立即终止发出调用的进程。所有属于该进程的文件描述符都关闭。如果该进程拥有子进程,那么父子进程关系被转到init进程上。被结束的进程将收到来自子进程的僵死信号SIGCHLD。如果被结束的进程在控制台或终端上运行,shell程序将收到SIGHUP信号。
函数中的参数status是返回给父进程的状态值,父进程可通过wait系统调用获得。status只有最低1个字节能被父进程读取,由此可知,实际值域范围为0至255。
系统调用_exit()没有返回值,被终止进程不会知道该调用是否成功。另外,该调用不会刷新输入输出缓冲区,因此进程结束前必须自己刷新缓冲区,或者改用exit()系统调用。exit()系统调用将进行一些上下文清理工作,例如释放所有占用的资源、清空缓冲区等。
(2).进程等待系统调用
格式:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_twaitpid(pid_tpid, int *status, int options);
说明:
wait
()等待任一僵死的子进程,将子进程的退出状态(退出值、返回码、返回值)保存在参数
status
中。即进程一旦调用了
wait
,就立即阻塞自己,由
wait
分析是否当前进程的某个子进程已经退出,如果找到这样一个已经变成僵尸的子进程,
wait
就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,
wait
就会一直阻塞在这里,直到有一个出现为止。若成功,返回该终止进程的
PID
;否则返回
-1
。
waitpid
()等待标识符为
pid
的子进程退出,将该子进程的退出状态(退出值、返回码、返回值)保存在参数
status
中。参数
options
规定调用的行为,
WNOHANG
表示如果没有子进程退出,立即返回
0
;
WUNTRACED
表示返回一个已经停止但尚未退出的子进程信息。
例程如下:
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork failed");
exit(1);
}
if (pid == 0)
{
int i;
for (i = 3; i > 0; i--)
{
printf("This is the child\n");
sleep(1);
}
exit(3);
}
else
{
int stat_val;
waitpid(pid, &stat_val, 0);
if (WIFEXITED(stat_val))
printf("Child exited with code %d\n", WEXITSTATUS(stat_val\
));
}
return 0;
}
从运行结果分析程序的运行过程如下所示。
¨
(
1
)父进程初始化。
¨
(
2
)父进程调用
fork
,
fork
为系统调用,因此进入内核。如果
fork
不成功,系统显示“
fork failed”
,如果成功,内核父进程复制出一个子进程。本示例是
fork
成功,所以现有父子两个一模一样进程。但是父进程先返回还是子进程先返回,取决于内核的调度算法。
¨
(
3
)如果子进程先返回,返回值
pid
为
0
,执行
if (
pid
== 0)
后的语句,显示三次字符串“
This is the child”
,
exit
(
3
)表示退出
status
为
3
。
(4)如果是父进程先返回,返回值pid是子进程的id号,一个大于0的数,执行if (pid == 0) else后的语句。waitpid(pid, &stat_val, 0)会阻塞父进程等待pid指定的子进程退出,此时CPU空闲,系统会调度子进程执行,显示三次字符串“This is the child”, 退出status为3,子进程结束后唤醒父进程,显示Child exited with code 3。
wait函数系统调用的宏定义:
(3).系统睡眠函数(sleep函数)
系统调用
sleep
用来使进程主动进入睡眠状态,该函数的一般形式是:
sleep(
秒数
);
执行该系统调用后,进程将进入睡眠状态,直到指定的秒数已到。正常情况下,该调用的返回值为
0
,若是因为被信号所唤醒,则返回值为原始秒数减去已睡眠秒数的差。
(4).获得指定进程的标识符(getpid函数、getppid函数)
格式:
#include <sys/types.h>
#include <unistd.h>
pid_tgetpid(void);
pid_tgetppid(void);
说明:getpid()返回调用该系统调用的进程的id号,getppid()返回调用该系统调用的进程父进程的id号。
例程:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("Current process ID:%d\n",(int)getpid());
printf("Parent process ID:%d \n",(int)getppid());
return 0;
}
(5).设置进程的信息(setuid函数、setgid函数、setpgrp函数和setpgid函数)
设置进程的
UID
可使用
setuid
()
函数,设置进程的
GID
可使用
setgid
()
函数。这两个调用的一般形式如下:
int
setuid
(
uid_t
uid
);
int
setgid
(
gid_t
gid
);
setuid
()
函数可修改发出调用进程的
UID
,参数
uid
为创建进程的用户信息。如果以普通用户的
UID
作为参数执行该调用,
Linux
内核将直接设置进程
UID
为参数
uid
信息。如果以根用户的
UID
作为参数,为了保障系统的安全性,
Linux
内核将以进程表和
u
区中用户真实的标识号来设置进程
UID
。
setuid
()
函数执行成功时,返回值为
0
,否则返回
–1
。
setgid
()
函数可修改发出调用进程的
GID
,与前者不同,该调用不会检验用户的真实身份。参数
gid
为进程的新
GID
信息。执行成功时,返回值为
0
,否则返回
–1
。
系统调用
setpgrp
()
和
setpgid
()
都是用来设置进程
PGID
,它们的一般形式为:
int
setpgrp
(void);
int
setpgid
(
pid_t
pid
,
pid_t
pgid
);
其中,
setpgrp
()
函数直接将进程的
PGID
设为与
PID
相同的数值,
setpgid
()
以其中参数修改
PGID
。参数
pid
为指定进程的
PID
,值为
0
时修改发出调用进程的
PGID
。参数
pgid
为指定的
PGID
信息,值为
0
时,修改所有
PID
与参数
pid
相等的进程,将这些进程的
PGID
值设为参数
pgid
的值。若以普通用户权限发出此调用,而
PGID
原本为根用户组所有,那么只有在指定进程与调用进程的
EUID
相同时,或者指定进程为调用进程的子进程时才有效。
(6).修改进程的工作目录(chdir函数)
文件操作部分曾介绍过
chdir
()
系统调用,该调用对于进程控制有不同的意义。
chdir
()
函数将进程的当前工作目录改为由参数指定的目录。该调用的一般形式如下:
int
chdir
(const char
*
path);
参数
path
为指定目录的路径,发出该调用的进程必须具备该目录的执行权限。调用成功时返回值为
0
,否则返回
–1
,并设置相应的错误代码。
(7).根交换操作(chroot函数)
系统调用
chroot
又被称为根交换操作,作用通常是在一个
Linux
系统上虚拟另一个
Linux
系统,根交换后,所有的命令操作都被重新定向。该调用的一般形式如下:
int
chroot
(const char
*
path);
参数
path
为新的根目录路径,执行后,进程将以该目录作为根目录,并且使进程不能访问该目录以外的内容。该操作不改变当前工作目录,如果当前工作目录在指定目录以外,则无法访问其中内容。根交换操作只能由根用户发出,调用成功时返回值为
0
,错误时返回
–1
,并设置相应的错误代码。
(8).修改进程的优先级(nice函数)
系统调用
nice()
用来改变进程的优先级。该调用的一般形式如下:
int
nice(
int
inc);
参数
inc
为调用
nice()
函数的进程优先级数值的增量。优先级数值越低的值,被调度上
CPU
运行的机会越大;优先级数值越高,被调度上
CPU
运行的机会越低。但是,只有根用户能为
inc
参数设置负值,使进程优先级提高,普通用户设置的正值会降低优先级。当调用成功时,返回值为
0
,否则返回
–1
。