进程通信信号,进程对信号的处理方式
这表明进程1357处于运行状态(R)并且正在执行命令ps -ax。所以这个过程是在它自己的输出中描述的。状态指示器仅指示程序已准备好运行,不一定在实际运行中。在单处理器计算机上,一次只能运行一个进程,而其他进程必须依次等待。这些序列称为时间片,非常短,给我们一种所有程序都在同时运行的感觉。r状态仅仅意味着程序没有等待其他进程完成或者输入或输出完成。这就是为什么我们在ps输出中看到两个这样的过程。(另一个通常被认为正在运行的进程是X display服务器。)
Linux内核使用进程调度器来决定哪个进程将接受下一个时间片。他通过使用进程优先级做到了这一点(我们在第4章讨论了优先级)。具有高优先级的进程将具有较高的运行频率,而其他进程,例如具有低优先级的后台任务,将具有较低的运行频率。在Linux中,进程不能超过分配给它们的时间片。旧系统,如Windows 3.x,通常需要进程出现,以便其他进程可以再次运行。
在多任务系统中,比如Linux,多个程序可能会竞争同一个资源。那些执行大量任务并暂停输入的程序被认为比持续计算一些值或持续查询系统以查看新输入是否可用的专用处理器好得多。用术语来说,我们称之为好程序,从常识来说,这种‘好’是可以衡量的。操作系统根据一个‘nice’值和程序的行为来决定一个进程的优先级,它的默认值是0。长时间不间断运行的程序通常具有较低的优先级。例如,程序暂停以等待输入得到满足。这有助于保持程序与用户的交互;当它等待用户的输入时,系统将提高它的优先级,这样当它满足重新操作的条件时,它将具有更高的优先级。我们可以使用nice程序来设置流程的nice值,并使用renice来重新调整流程的nice值。nice命令将进程的nice值增加10,从而为其分配较低的优先级。我们可以使用ps命令的-l或-f选项来检查活动进程的nice值。我们感兴趣的值显示在NI列中。
$ ps -l
PPID ADDR深圳武汉TTY时间CMD
000S 500 1259 1254 0 75 0-710 wait 4 pts/2 00:00:00:00
000S 500 1262 1251 0 75 0-714 wait 4 pts/1 00:00:00:00 bash
000S 500 1313 1262 0 75 0-2762 schedu pts/1 00:00:00:00 emacs
000S 500 1362 1262 2 80 0-789 schedu pts/1 00:00:00点
000 R 500 1363 1262 0 81 0-782-pts/1 00:00:00 PS
从这里我们可以看到,oclock程序使用默认的nice值运行。如果他是由以下命令启动的
晚上好
那么他被赋予了一个很好的值10。如果我们使用以下命令进行调整
$ renice 10 1362
1362:旧优先级0,新优先级10
那么oclock运行的频率就会降低。我们可以再次使用ps命令来查看修改后的nice值:
PPID ADDR深圳武汉TTY时间CMD
000S 500 1259 1254 0 75 0-710 wait 4 pts/2 00:00:00:00
000S 500 1262 1251 0 75 0-714 wait 4 pts/1 00:00:00:00 bash
000S 500 1313 1262 0 75 0-2762 schedu pts/1 00:00:00:00 emacs
000S 500 1362 1262 0 90 10-789 schedu pts/1 00:00:00:00点
000 R 500 1365 1262 0 81 0-782-pts/1 00:00:00 PS
status列现在包含n,表示nice值已被默认值修改。ps的输出PPID字段指示父进程ID,因此如果该进程不再运行,由PID进程启动的进程是init(PID 1)。
Linux调度程序根据优先级决定允许哪个进程运行。当然每个实现会有所不同,但是高优先级有更高的运行频率。在某些情况下,如果高优先级进程准备运行,低优先级进程将根本不会运行。
开始一个新的过程。
我们可以让一个程序从另一个进程内部运行,从而利用系统库函数创建一个新的进程。
#包含stdlib.h
int system(const char * string);
系统函数运行作为字符串传递给它的命令,并等待它结束。该命令的运行结果与以下命令的运行结果相同:
$ sh -c字符串
如果shell没有开始运行这个命令,系统将返回127,如果出现其他错误,将返回-1。否则,系统返回该命令的退出代码。
测试系统
我们可以使用system编写一个程序来为我们运行ps命令。虽然这个程序不是很有用,但是我们将在后面的例子中看到如何开发这项技术。在这个例子中,我们不严格检查系统调用是否适用于这种情况。
#包含stdlib.h
#包含stdio.h
int main()
{
printf("用system/n运行PS ");
系统(" PS-ax ");
printf("完成。/n ");
退出(0);
}
当我们编译并运行这个程序system1.c时,我们将得到以下输出:
$ ./系统1
使用系统运行ps
PID TTY统计时间命令
1 ?S 0:05初始化
2 ?SW 0:00 [keventd]
.
1262分/秒0:00/箱/巴什
1273分/秒0:00苏-
1274分/秒0:00 -bash
1463点/1秒0点钟-透明-几何图形135x135-10 40
1465分/秒0:01 emacs Makefile
1480分/1秒0点。/系统1
1481 pts/1 R 0:00 ps -ax
完成了。
因为system1函数使用shell来启动所需的程序,所以我们可以通过修改system1.c中的函数调用来在后台运行它:
系统(" ps -ax
当我们编译并运行这个版本的程序时,我们将得到以下输出:
$ ./系统2
使用系统运行ps
PID TTY统计时间命令
1 ?S 0:05初始化
2 ?SW 0:00 [keventd]
.
完成了。
$ 1246 ?s 0:00 kdeinit:klipper-icon klipper-mini icon klipper
1274分/秒0:00 -bash
1463点/1秒0点钟-透明-几何图形135x135-10 40
1465分/秒0:01 emacs Makefile
1484 pts/1 R 0:00 ps -ax
在第一个例子中,程序使用 ps -ax 字符串来调用system,它将运行ps程序。当ps命令完成后,我们的程序将把这个调用返回给系统。系统程序很有用,但是很有限。因为我们的程序要等到系统调用启动的进程结束,不能做其他的任务。
在第二个示例中,系统调用在shell命令结束时立即返回。因为他要求在后台运行一个程序,当ps程序启动时,shell会立即返回,就像我们在shell提示符下输入以下命令一样:
$ ps -ax
系统2程序输出完成。并且在ps命令有机会完成其所有输出之前退出。系统2退出后,ps命令将继续生成输出。这个过程的行为会让用户感到困惑。为了更好地利用流程,我们需要更好地控制其操作。让我们来看看进程的底层接口,exec。
注意:一般来说,system并不是启动其他进程的完美方式,因为它使用一个shell来调用所需的程序。这样效率不高,因为shell是在程序启动之前启动的,而且依赖于shell的安装和环境。在这一节中,我们将看到一个调用程序的更好的方法,它总是优先于系统调用。
替换流程图像。
有一系列以exec开头的等价函数。它们的不同之处在于它们启动进程和列出程序参数的方式。exec函数用path和file参数指定的新进程替换当前进程。
#包括unistd.h
char * * environ
int execl(const char *path,const char *arg0,(char *)0);
int execlp(const char *file,const char *arg0,(char *)0);
int execle(const char *path,const char *arg0,(char *)0,char *const
envp[]);
int execv(const char *path,char * const argv[]);
int execvp(const char *file,char * const argv[]);
int execve(const char *path,char *const argv[],char * const envp[]);
这些功能属于两类。l,execl,execcle接受多个参数并以空指针结束。execv和execvp的第二个参数是一个字符串数组。在这两种情况下,argv数组中出现的指定参数都被传递给main,并启动一个新程序。这些函数通常使用execve实现,尽管并不要求以这种方式实现。
带p后缀的函数与其他函数不同,它们会查找PATH环境变量来查找新的程序可执行文件。如果可执行文件不在这个路径中,您需要将包含目录的绝对文件名作为参数传递给函数。
全局环境变量可以为新的程序环境传递一个值。相应的,execle和execve的另一个参数可以传递一个字符串数组作为新的程序环境。
如果我们想使用exec函数来启动ps程序,我们需要在6个exec函数族中进行选择,如下面的代码段所示:
#包括unistd.h
/*参数列表示例*/
/*注意,我们需要一个argv[0] */的程序名
char *const ps_argv[]={ps ,-ax ,0 };
/*示例evnironment,不是很有用*/
char * const PS _ envp[]={ PATH=/bin:/usr/bin , TERM=console ,0 };
/*可能调用exec函数*/
execl(/bin/ps , ps ,-ax ,0);
execlp(ps , ps ,-ax ,0);/*假设ps在/bin */
execle(/bin/ps , ps ,-ax ,0,PS _ envp);/*通过自己的环境*/
execv(/bin/ps ,PS _ argv);
execvp(ps ,PS _ argv);
execve(/bin/ps ,ps_argv,PS _ envp);
测试执行
让我们修改我们的例子来使用execlp调用。
#包括unistd.h
#包含stdio.h
int main()
{
printf("用execlp/n运行PS ");
execlp("ps "," ps ","-ax ",0);
printf("完成。/n ");
退出(0);
}
当我们运行这个程序pexec.c时,我们将输出到通常的ps,但是没有完成。一点信息都没有。我们还应该注意,输出中没有名为pexec的进程。
$ ./pexec
用execlp运行ps
PID TTY统计时间命令
1 ?S 0:05初始化
2 ?SW 0:00 [keventd]
.
1262分/秒0:00/箱/巴什
1273分/秒0:00苏-
1274分/秒0:00 -bash
1463点/1秒0点钟-透明-几何图形135x135-10 40
1465分/秒0:01 emacs Makefile
1514点/1r 0:00 PSax
程序首先输出它的第一条消息,然后调用execlp,它将在PATH环境变量指定的目录中查找名为ps的程序。然后他执行这个程序来替换我们的pexec程序,就像我们输入下面的shell命令一样。
$ ps -ax
当ps结束时,我们得到一个新的shell提示符。我们没有返回pexec,所以第二条消息根本不会输出。新进程的PID与原始进程的相同,并且具有与父进程相同的PID和nice值。实际上,所发生的是,正在运行的程序已经通过exec调用中指定的新的可执行文件开始执行新的代码。
参数列表的组合大小和exec函数启动的进程环境是有限的。这是由ARG_MAX指定的,而在Linux系统上这个限制是128KB。其他系统可能会设置更宽松的限制,但这可能会导致问题。POSIX规范指出ARG_MAX至少应为4096B。
通常,除非发生错误,否则exec函数不会返回,在这种情况下,将设置错误变量errno,exec函数将返回-1。
exec启动的新进程将继承原进程的许多特征。特别是,打开文件描述符将在新进程中保持打开,除非设置了关闭选项。在原始进程中打开的目录流将被关闭。
复制流程图像。
为了让一个进程同时执行多个功能,我们要么使用线程,要么像init那样在程序内部创建一个完全独立的进程,而不是像exec那样替换当前执行的线程。
我们可以通过调用fork来创建一个新的流程。此系统调用将复制当前流程,并在流程表中创建一个与当前流程具有相同属性的实体。新进程几乎与原始进程相同,执行相同的代码,但有自己的数据空间、环境和文件描述符。结合exec函数,fork是我们创建一个新流程所需要的。
#包含sys/types.h
#包括unistd.h
PID _ t fork(void);
正如我们在图11-2中看到的,父进程中的fork调用将返回新的子进程的PID。新流程将继续执行,就像原始流程一样,只是子流程中的fork调用返回0。这可以区分父进程和子进程。
如果fork失败,它将返回-1。这通常是由父进程可以拥有的子进程数量(CHILD_MAX)造成的。在这种情况下,errno将被设置为EAGAIN。如果进程表中没有足够的空间,或者没有足够的虚拟内存,errno变量将被设置为ENOMEM。
使用fork的常用代码片段如下:
pid _ t new _ pid
new _ PID=fork();
开关(新pid) {
案例1 : /*错误*/
打破;
案例0 : /*我们是孩子*/
打破;
默认:/*我们是父母*/
打破;
}
测试叉
让我们看一个简单的例子,fork1.c
#包含sys/types.h
#包括unistd.h
#包含stdio.h
int main()
{
pid _ t pid
char * message
int n;
printf("fork程序开始/n ");
PID=fork();
开关(pid)
{
案例1:
perror(“叉叉失败”);
出口(1);
案例0:
message="这是孩子";
n=5;
打破;
默认值:
message="这是家长";
n=3;
打破;
}
for(;n n - ) {
puts(消息);
睡眠(1);
}
退出(0);
}
这个程序将运行两个进程。将创建一个子流程,并将输出一条消息五次。原来的流程只输出三次。父进程将在子进程输出其所有信息之前结束,因此下一个shell提示符将与输出混合出现。
$ ./fork1
分叉程序开始
这是家长
这就是那个孩子
这是家长
这就是那个孩子
这是家长
这就是那个孩子
$这是孩子
这就是那个孩子
当fork被调用时,程序被分成两个独立的进程。父进程由fork返回的非零值标识,父进程用于设置要输出的信息数量。