Linux应用程序开发——进程控制

一、Linux系统进程概述

  • (1) 进程的定义:进程在Linux操作系统中执行特定的任务,它是处于活动状态的计算机程序,而程序可以理解为存储在磁盘上的可执行计算机指令与数据的静态实体,它包含当前处理器中的活动状态。进程包括了程序计数器与CPU所有寄存器的值,同时它的子程序中存储着子程序参数、返回地址与变量等临时数据。
  • (2)进程的作用:一个进程在其生命周期内使用系统中的资源,它通过CPU来执行命令,在物理内存中存放指令与数据。它使用文件系统提供的功能打开并使用文件,直接或者间接地使用物理设备。Linux系统必须跟踪每个进程与系统资源,以便实现进程间资源的公平分配。
  • (3)进程的多处理机制:系统中最宝贵的资源就是CPU,Linux系统是一个多处理操作系统,即操作系统可以同时存在多个进程,当某个进程开始等待时,操作系统将CPU控制权交给其他进程。其中的调度任务是由调度器完成的,Linux系统采用一些调度策略来保证CPU分配的公平性。

二、Linux系统进程调度

  • 调度集成时,要在所处可运行状态的进程中选择最值得运行的进程,这就出现一个问题:什么进程才是最值得运行的?
  • 每个进程都有一个tast_struct结构,该结构包含policy、priority、counter与rt_priority四项,它们是进程选择的依据:
  • (1)policy是进程的调度策略,用以区分实时进程与普通进程;
  • (2)priority是实时进程与普通进程的静态优先级;
  • (3)counter是进程剩余的时间片,其起始值就是priority的值;它还可用于计算进程运行程度goodness;
  • (4)rt_priority是实时进程特有的,用来实现进程间的选择。
  • 对于普通进程,Linux采用动态优先调度,选择进程的根据counter的大小。
  • (1)在进程刚被创建时,优先级priority被赋予一个初值(0~70),同时将priority的值传递给counter参数。在这种情况下,priority参数与counter参数具有同样的含义——进程的时间片。其中priority代表分配给集成的时间片,而counter中表示进程剩余的时间片。
  • (2)在进程运行过程中,counter不断减小,而priority则保持不变,当counter变为0时对counter重新赋值。在普通进程运行过程中,counter的减小给了其他进程运行的机会,直至counter为0时才完全放弃对CPU的使用,这就相当于优先级在动态变化,所以普通进程的调度才被称为动态优先调度。
  • 对于实时进程,Linux采用两种调度策略:FIFO(先进先出服务调度)与RR(时间片轮转调度)。与普通进程不同,实时进程的counter只是用来表示该进程剩余时间片,而不作为衡量它是否值得运行的标准。
  • 对于普通进程与实时进程,Linux系统中的goodness()函数给出了衡量进程运行的程度。该函数综合以上提到的各个方面,并给每个出于运行状态的进程赋予一个权值(weight),进程调度以这个权值作为选择进程的唯一标准。因此,goodness()函数使得Linux系统各种进程调度变得简单易行。
  • Linux根据policy的值将进程总体上氛围实时进程与普通进程,并提供三种调度算法:一种传统的Unix调度程序与两个有POSIX.1b操作系统标准所规定的调度程序。
  • 非实时进程有两种优先级:静态优先级与动态优先级;实时进程又增加了第三种优先级:实时优先级。其中,优先级是一个简单的整数,它决定了哪个进程使用CPU的资源,优先级越高得到CPU的时间的机会也越大。
  • (1)静态优先级(priority):优先级的值不随时间改变,只能由用户修改。它指明了在被迫与其他进程竞争CPU之前,该进程允许使用的最大时间片;
  • (2)动态优先级(counter):只要进程拥有CPU,它随着时间不断减小,当counter的值为0时,则进程重新调用。
  • (3)实时优先级(rt_priority):指明该进程自动把CPU交给其他进程,较高权值的进程总是优于低权值的进程。如果一个进程不是实时进程,其优先级就是0,所以,实时进程总是优于非实时进程。
  • 在进程调度过程中,policy可以为以下值:
  • (1)SCHED_OTHER:一种普通的用户进程,是进程的缺省类型,采用动态有限调度策略,选择进程的依据主要是进程goodness值的大小。这种进程在运行时,可以被高goodness值的进程抢占。
  • (2)SCHED_FIFO:一种实时进程,它遵循POSIX.1b标准的FIFO调度规则。它会一直运行,直到有一个进程因I/O阻塞,或者主动释放CPU,或者CPU被另一个具有更高rt_priority的实时进程抢占。
  • (3)SCHED_RR:一种实时进程,遵循POSIX.1b标准的RR(循环round-robin)调度规则。除了时间片有些不同外,这些策略与SCHED_FIFO类似。当SCHED_RR进程的时间片用完后,就被收到SCHED_FIFO与SCHED_RR队列的末尾。

三、相关系统调用及实例分析

3.1 系统调用概述与常用函数

  • 系统调用是操作系统提供给用户程序调用的特殊接口,用户程序可以通过该接口或得操作系统内核提供的服务。从逻辑上看,系统调用可以被理解为一个内核与用户空间程序交互的接口,它将用户进程的请求传递给内核,待内核把请求处理后再将处理结果传给用户空间。
  • 系统服务之所以需要通过系统调用提供给用户空间的根本原因是系统的保护机制。Linux系统的运行空间分为内核空间与用户空间,这两个空间是运行在不同级别的,逻辑上相互隔离,因此用户进程通常是不允许访问内核数据的,也无法使用内核函数。用户进程只能在用户空间操作用户数据,调用用户空间函数。下表列出了Linux系统中与进程相关的系统调用函数:
系统调用函数功能
fork创建一个新进程
clone按指定条件创建子进程
execve运行可执行文件
exit终止进程
_exit立即终止当前进程
getdtablesize进程所能打开的最大文件数
getpgid获取指定进程组标识号
setpgid设置指定进程组标识号
getpgrp获取当前进程组标识号
setpgrp设置当前进程组标识号
getpid获取进程标识号
getppid获取父进程标识号
getpriority获取调度优先级
setpriority设置调度优先级
modify_ldt读/写进程本地描述表
nanosleep使进程睡眠指定的时间
nice改变分时进程的优先级
pause挂起进程等待信号
personality设置进程运行域
prctl对进程进行特定操作
ptrace进程跟踪
sched_get_priority_max获取静态优先级的上限
sched_get_priority_min获取静态优先级的下限
sched_getparam获取进程调度参数
sched_getscheduler获取指定进程的调度策略
sched_rr_get_interval获取按照RR算法调度的实时进程的时间片长度
sched_setparam设置进程调度参数
sched_setscheduler设置指定进程的调度策略与参数
sched_yield进程主动让出处理器,并将自己调度到等候队列队尾
vfork创建一个子进程,用以执行新的程序,通常与execve结合使用
wait/wait3等待子进程终止
waitpid/wait4等待指定子进程终止
capget获取进程权限
capset设置进程权限
getsid获取会晤标识号
setsid设置会晤标识号

3.2 系统调用实例

  • 下面给出一些重用的系统调用的实例分析:
    1、getpid
  • getpid的作用是获取进程的ID,它在Linux函数库中的原型为:
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void);
  • 再参考下面的实例:
/*getpid_test.c*/
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
	printf("The current process ID is %d\n",getpid());
	exit(0);
}
  • 其中,pid_t类型即为进程ID的类型,在i386架构上,pid_t的类型与int类型是完全兼容的,可以用处理整数的方法去处理pid_t,比如可以用%d的方式打印出来。下面在shell脚本中编译并运行程序getpid_test.c:
$ gcc getpid_test.c -o getpid_test
$ ./getpid_test
The current process ID is 1871
  • 再运行一遍:
$ ./getpid_test
The current process ID is 1872
  • 可见,尽管是同一个应用程序,每次运行所分配的进程标志符都不尽相同。
    2、fork
  • fork在系统调用中的作用为复制一个进程,当一个进程调用fork时,将会出现两个一样的进程,也由此得到一个新的进程,它在Linux函数库中的原型为:
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
  • Linux系统中,fork是创造进程的唯一方法,其他一些库函数比如system()实际上是在内部调用fork函数实现进程的创建的。
/*fork_test.c*/
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
main()
{
	pid_t pid;	/*此时只有一个进程*/
	pid=fork();	/*此时已经有两个进程在同时运行*/
	if(pid<0)
		printf("error in fork!");
	else if(pid==0)
		printf("The child process ID is %d",getpid());
	else
		printf("The parent process ID is %d",getpid());
}
  • 在shell脚本中编译运行:
$ gcc fork_test.c -o fork_test
$ ./fork_test
The parent process ID is 1873
The child process ID is 1874
  • fork_test程序中在执行pid=fork()之前只有一个进程,但是执行完该语句后,就变成了两个进程在执行了。这两个进程中原来存在的那个进程称为父进程,新出现的进程为子进程。父进程与子进程的区别在于进程标志符(process ID)不同外,变量pid的值也不相同,pid存放的是fork的返回值。需要注意的是,fork被调用一次,却能够返回两次,它可能有三种不同的返回值:
  • (1)在父进程中,fork返回新创建子进程的进程ID;
  • (2)在子进程中,fork返回0;
  • (3)如果出现错误,则返回一个负值。
    3、exit
  • (1)exit在Linux函数库中的原型为:
#include<stdlib.h>
void exit(int status);
  • exit系统调用函数为终止一个进程,无论在程序什么位置,只要执行到exit系统调用,进程就会停止剩下的所有操作,清楚包括PCB在内的各种数据结构,请看如下的程序实例:
/*exit_test1.c*/
#include<stdlib.h>
#include<stdio.h>
main()
{
	printf("this process will exit!");
	exit(0);
	printf("never be displayed!");
}
  • 在shell脚本中编译运行exit_test1程序:
$ gcc exit_test1.c -o exit_test1
$ ./exit_test1
This process will exit!
  • 程序并没有打印出“never be displayed!”,因为在exit(0)执行后,进程就已经终止了。exit系统调用带有一个整数类型的参数status,该参数传递进程结束时的状态:正常结束或出现异常的结束。通常,0表示正常结束;其他数值表示出现错误。
  • (2)_exit与exit具有微小的差别,主要体现在它们在库函数中的定义。_exit在Linux库函数中的原型为:
#include<unisstd.h>
void _exit(int status);
  • 与exit比较一下,exit()函数定义在stdlib.h库中,而_exit()函数定义在unistd.h库中。实际上,_exit()函数的作用相对简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;而exit()函数则在这些基础上做了进一步地包装,在执行退出之前加了若干工序,其中最主要的工序为:exit()调用exit系统调用之前检查文件的打开情况,把文件缓冲区中的内容写回文件。
  • 在Linux标准函数库中有一套高级I/O,它们通常包括:printf()、fopen()、fread()、fwrite()等。它们也被称作缓冲I/O(buffered I/O),其特征是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会多读出若干条记录,这样下次读文件时就可以直接从内存的缓冲区读取;每次写文件时,仅吸入内存中的缓冲区,等满足一定条件后(达到缓冲区最大容量,或遇到特定的结束字符),再将缓冲区中的内容一次性写入文件。如果有一些数据,人们认为已经写入了文件,实际上由于没有满足特定条件,而使得它们只保留在缓冲区内,此时使用_exit()函数直接关闭进程,则缓冲区中的数据就会丢失。反之,如果想保证数据的完整性,就一定要使用exit()函数。
    4、exec
  • 系统调用exec是用来执行一个可执行文件来代替当前进程的执行映像。需要注意的是,该调用并没有生成新的进程,而是在原有进程的基础上,替代原有进程的正文,调用前后是同一个进程,进程号PID不变,但执行的程序变了。它们又6种调用形式,其声明格式如下所示:
(1) int execl(const char *path,const char *arg,..);
(2) int execlp(const char *file,const char *arg,...);
(3) int execle(const char *path,const char *arg,...,char* const envp[]);
(4) int execv(const char *path,char *const argv[]);
(5) int execve(const char *filename,char *const argv[],char *const envp[]);
(6) int execvp(const char *file,char *const argv[]);
  • 在使用这些系统调用的程序中需要加入一下头文件与外部变量:
#include<unistd.h>
extern char **environ;
  • 以系统调用execve为例,参数path是将要执行的文件,参数argv是要传递给文件的参数,参数envp是要传递给文件的环境变量。当参数path所指的文件替换原进程的执行映像后,文件path开始执行,参数argv与envp便传递给进程。
  • 为了使得用户方便与灵活地使用shell、Linux引入环境的概念。环境是一些数据,用户可以改变或删除这些数据,这些数据被称为环境变量。每个用户都可以有自己的环境变量,用户可以用env命令查看环境变量。下面给出打印出传递给该进程的所有参数与环境变量的例子:
/*example.c*/
#include<unistd.h>
#include<stdio.h>
extern char **environ;
int main(int argc,char* argv[])
{
	int i;
	printf("Argument:\n");
	for(i=0;i < argc;i++)
		printf("Arg%d is %s\n",i, "argv[i]\n");
	printf("Environment:\n");
	for(i=0;environ[i]!=NULL;i++)
		printf("%s,environ[i]")	;
}
  • 下面在shell脚本中运行程序:
$ gcc example.c -o example
$ ./example test
Argument:
Arg0 is ./example
Arg1 is test
Evironment:
PWD=/root
REMOTEHOST=wj
HOSTNAME=Liang
HOME=/root
...
  • 系统调用exec与fork配合使用,父进程fork一个子进程,在子进程中调用exec来替代子进程的执行映像,并发地执行一些操作。
    5、wait
  • 系统调用wait的功能是发出调用的进程如果有子进程,则睡眠直至它们中的一个终止为止,该调用声明的格式如下:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status)
pid_t waitpid(pid_t pid, int *status, int options);
  • 发出wait调用的进程进入睡眠直到它的一个子进程退出或收到一个不能被忽略的信号时被唤醒。如果调用发出时,已经有退出的子进程,该调用立即返回。其中调用返回时参数status中包含子进程的状态信息。
  • 调用waitpid与wait的区别为waitpid等待由参数pid指定的子进程退出。其中参数pid的含义与取值方法如下:
  • (1)参数pid<-1时,当退出的子进程组ID等于pid的绝对值时结束等待。
  • (2)参数pid=0时,该子进程的进程组ID等于发出调用进程的进程组ID时,子进程退出。
  • (3)参数pid>0时,等待进程ID等于参数pid的子进程退出。
  • (4)参数pid=-1时,相当于调用wait,等待任何子进程退出。
  • 对于调用waitpid中的参数options的取值以及含义如下所示:
  • (1)WNOHANG:如果没有子进程退出就立刻返回。
  • (2)WUNTRANCED:对于已经停止但未报告状态的子进程,该调用也从等待中返回和报告状态。
    6、sleep
  • 函数调用sleep可以用来使进程挂起指定的秒数,该函数调用的声明格式如下:
#include<unistd.h>
unsigned int sleep(unsigned int seconds)
  • 该函数调用使得进程挂起一个指定时间,知道指定时间用完或者接收到信号,Linux系统是用SIGALRM实现的。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值