`
isiqi
  • 浏览: 16348122 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

进程与信号(三)

阅读更多

等待一个进程

当我们使用fork启动一个子进程时,他具有其自己的生命周期并且独立运行。有时,我们希望知道一个子进程何时结束。例如,在前一个例子中,父进程在子进程之前结束,从而我们得到混乱的输出,因为子进程还在继续运行。我们可以通过调用wait来使得父进程在继续运行之前等待,直到子进程结束。

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *stat_loc);

wait系统调用暂停父进程,直到他的一个子进程结束。这个函数调用返回其子进程的PID。这通常是一个已经结束的子进程。状态信息使得父进程可以确定子进程的结束状态,也就是,由main返回或是传递给exit的值。如果stat_loc不是一个空指针,状态信息就会被写入他所指向的地址。

我们可以使用定义在sys/wait.h中的宏来解释这些状态信息。这个宏包括:

宏 定义
WIFEXITED(stat_val) 如果子进程正常结束为非0
WEXITSTATUS(stat_val) 如果WIFEXITED为非0,这会返回子进程的结束代码
WIFSIGNALED(stat_val) 如果子进程是因为一个未捕获的信号而结束的,则为非0
WTERMSIG(stat_val) 如果WIFSIGNALED为非0,这会返回一个信号编号
WIFSTOPPED(stat_val) 如果子进程已经结束则为非0
WSTOPSIG(stat_val) 如果WIFSTOPPED为非0,这会返回一个信号编号

试验--wait

让我们来简单的修改一下我们的程序,从而我们可以等待并且检测子进程的结束状态。我们将其命名为wait.c。

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
char *message;
int n;
int exit_code;
printf(“fork program starting\n”);
pid = fork();
switch(pid)
{
case -1:
perror(“fork failed”);
exit(1);
case 0:
message = “This is the child”;
n = 5;
exit_code = 37;
break;
default:
message = “This is the parent”;
n = 3;
exit_code = 0;
break;
}
for(; n > 0; n--) {
puts(message);
sleep(1);
}

下面的程序部分等待子进程结束。

if (pid != 0) {
int stat_val;
pid_t child_pid;
child_pid = wait(&stat_val);
printf(“Child has finished: PID = %d\n”, child_pid);
if(WIFEXITED(stat_val))
printf(“Child exited with code %d\n”, WEXITSTATUS(stat_val));
else
printf(“Child terminated abnormally\n”);
}
exit(exit_code);
}

如果我们运行这个程序,我们就会看到父进程等待子进程。

$ ./wait
fork program starting
This is the child
This is the parent
This is the parent
This is the child
This is the parent
This is the child
This is the child
This is the child
Child has finished: PID = 1582
Child exited with code 37
$

工作原理

父进程由fork调用中得到一个返回的非0值,他使用wait系统调用挂起其自己的运行子进程的状态信息变得可用为止。这会在子进程调用exit时发生,我们为其指定一个返回代码37。然后父进程继续执行,通过检测wait调用所返回的值确定子进程正常结束,然后由状态信息解出结束代码。

僵尸进程

使用fork创建进程非常有用,但是我们必须跟踪子进程。当一个子进程结束时,他与其父进程的关联依然存在,直到父进程正常结束或是调用wait。进程中的子进程实体因而并没有立即释放。尽管已经不再是活动状态,子进程仍然存在于系统当中,因为需要存储退出代码,以防止父进程调用wait函数。他就变成所谓的死亡状态,或是一个僵尸进程。

如果我们在上面的fork示例程序中改变消息数量,我们就可以看到僵尸进程的创建。如果子进程输出的消息少于父进程,那么就会首先结束,并且退出成为一个僵尸进程,直到其父进程结束。

试验--僵尸进程

fork2.c与fork1.c相类似,所不同的是子进程所输出的消息数量,而父进程所输出的消息数量不变。下面是相关的代码:

switch(pid)
{
case -1:
perror(“fork failed”);
exit(1);
case 0:
message = “This is the child”;
n = 3;
break;
default:
message = “This is the parent”;
n = 5;
break;
}

工作原理

如果我们使用命令./fork2 &来运行上面的程序,并且在子进程结束之后父进程结束之前调用ps程序,我们就可以看到如下的输出(一些系统也许会称之为<zombie>而不是<defunct>)。

$ ps –al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
004 S 0 1273 1259 0 75 0 - 589 wait4 pts/2 00:00:00 su
000 S 0 1274 1273 0 75 0 - 731 schedu pts/2 00:00:00 bash
000 S 500 1463 1262 0 75 0 - 788 schedu pts/1 00:00:00 oclock
000 S 500 1465 1262 0 75 0 - 2569 schedu pts/1 00:00:01 emacs
000 S 500 1603 1262 0 75 0 - 313 schedu pts/1 00:00:00 fork2
003 Z 500 1604 1603 0 75 0 - 0 do_exi pts/1 00:00:00 fork2 <defunct>
000 R 500 1605 1262 0 81 0 - 781 - pts/1 00:00:00 ps

如果父进程非正常终止,子进程就会自动以PID 1的进程(init)为父进程。子进程现在是一个不再运行的僵尸进程,但是却继承自init,因为其父进程非正常终止。僵尸进程会保留在进程表直到被init进程所收集。进程表越大,这个过程就会越慢。我们需要避免僵尸进程,因为他们会消耗资源直到init对他们进行清理。

我们还可以使用另外一个系统调用来等待子进程。他就是waitpid,而我们要以使用这个函数调用来等待一个特定的进程终止。

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *stat_loc, int options);

pid参数指定我们要等待的特定的子进程的PID。如果他为-1,waitpid就会返回任何子进程的信息。与wait类似,他会将状态信息写入stat_loc所指向的位置(如果他不为空指针)。options参数可以允许我们修改waitpid的行为。最有用的选项为WNOHANG,这会阻止waitpid挂起调用者的执行。如果可以使用这个选项来查看是否有子进程已经结束,如果没有,则继续运行。其他的选项与wait相同。

所以,如果我们希望使得父进程来查看一个特定的子进程是否已经结束,我们可以使用下面的调用

waitpid(child_pid, (int *) 0, WNOHANG);

如果子进程还没有结束,这个调用会返回0,否则会返回child_pid。如果出错waitpid就会返回-1并且设置errno。如果并没有这个子进程(errno设置为ECHILD),或者如果这个调用被信号(EINTR)中断,或者如果选项参数不正确(EINVAL),则会出现错误。

输入与输出重定向

我们可以使用我们进程的知识,利用文件描述符在fork与exec调用之间保持不变的事实来修改程序的行为。下面的例子涉及一个过滤器程序--一个程序由标准输入读取并且输出到标准输出,在这个过程中执行一些有用的转换。

试验--重定向

下面是一个非常敌意的过滤器程序,upper.c,这个程序会读取输入并将其转换为大写。

#include <stdio.h>
#include <ctype.h>
int main()
{
int ch;
while((ch = getchar()) != EOF) {
putchar(toupper(ch));
}
exit(0);
}

当我们运行这个程序时,我们可以看到我们所期望的输出:

$ ./upper
hello THERE
HELLO THERE
^D
$

当然,我们也可以使用shell的重定向将一个文件转换为大写

$ cat file.txt
this is the file, file.txt, it is all lower case.
$ ./upper < file.txt
THIS IS THE FILE, FILE.TXT, IT IS ALL LOWER CASE.

如果我们希望在另一个程序中使用这个过滤器会怎么样呢?下面这个程序,useupper.c,接受一个文件名作为参数,而如果调用失败就会报告错误。

#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
char *filename;
if (argc != 2) {
fprintf(stderr, “usage: useupper file\n”);
exit(1);
}
filename = argv[1];

我们重新打开标准输入,当我们这样做时,我们会进行错误检测,然后使用execl调用upper。

if(!freopen(filename, “r”, stdin)) {
fprintf(stderr, “could not redirect stdin from file %s\n”, filename);
exit(2);
}
execl(“./upper”, “upper”, 0);

不要忘记,execl会替换当前的进程;如果没有错误,下面的代码行不会执行。

perror(“could not exec ./upper”);
exit(3);
}

工作原理

当我们运行这个程序时,我们可以指定一个文件将其转换为大写。这个工作是由程序upper来完成的,而这个程序并不处理文件名参数。注意我们并不需要upper的源代码;我们可以使用这种方式来运行任何可执行的程序:

$ ./useupper file.txt
THIS IS THE FILE, FILE.TXT, IT IS ALL LOWER CASE.

useupper程序使用freopen函数来关闭标准输入,并将文件名stdin与一个作为程序参数的文件相关联。然后他调用execl使用upper程序来替换当前的进程。因为打开的文件描述符在execl调用之间会保持不变,upper程序的运行就如同在shell命令下运行是一样的:

$ upper < file.txt

分享到:
评论

相关推荐

    进程间通信之信号 sinal ) 完整代码

    进程间通信之信号 sinal ) 唯一的异步通信方式 七种进程间通信方式: 一 无名管道( pipe ) 二 有名管道( fifo ) 三 共享内存 shared memory 四 信号 sinal 五 消息队列 message queue ) 六 信号量 ...

    Linux环境进程间通信 信号灯

    #### 三、信号灯与内核 **内核中的信号灯数据结构:** - **全局数据结构 `struct ipc_ids sem_ids`**:记录了所有信号灯的信息。 - **`struct kern_ipc_perm`**:描述具体的信号灯及其相关信息。其中,`struct sem`...

    实验 Linux进程通信的参考答案

    三、多进程并发执行 在 Linux 中,多进程并发执行可以通过 fork 函数来实现。fork 函数可以创建一个新的进程,该进程是父进程的副本。然后,父进程和子进程可以并发执行不同的任务。 在实验中,我们使用 fork 函数...

    《计算机操作系统》 进程的软中断通信

    它只是建立了进程与信号之间的联系,允许进程捕捉到信号并执行预先约定的动作。 第二个问题是,若子进程向父进程发送信号,父进程接到信号后可以缺省操作、或忽视信号、或执行一函数,各是什么含义?答案是,缺省...

    操作系统实验报告_进程同步与互斥.doc

    进程同步与互斥实验报告 操作系统实验报告《进程同步与互斥》实验的主要目的是掌握基本的进程同步与互斥算法,了解生产者-消费者问题,并学习使用 Windows 2000/XP 中基本的同步对象,掌握相关 API 的使用方法。...

    操作系统实验三进程同步

    ### 操作系统实验三:进程同步 #### 实验目的与要求 本次实验旨在帮助学生深入理解进程/线程同步的概念及应用方法,并学会利用进程/线程同步技术解决实际问题。具体目标包括: 1. **理解进程/线程同步原理**:通过...

    linux 网络编程 进程通信 信号量

    三、信号量 信号量是一种用于进程间同步和互斥的机制,它可以控制多个进程对共享资源的访问。在Linux中,信号量分为两种类型: 1. **互斥量(Mutex)**:用于实现简单的互斥访问,只有一个进程可以拥有互斥量并...

    操作系统第三章进程同步与通信.ppt

    "操作系统第三章进程同步与通信" 在计算机操作系统中,进程同步与通信是两个非常重要的概念。进程同步是指多个进程之间的协作,包括进程互斥、进程同步和进程通信三个方面。下面我们将详细介绍这三个方面的知识点。...

    UNIX Linux实验教程 3实验三Linux进程管理与控制.doc

    三、Linux 进程间的控制 * Linux 进程间的控制依靠发送信号的方式进行,一个进程需要向另一个进程发送控制信息时,就向其发送信号,接收信号的进程将对信号进行处理。 * 在程序代码中可以使用 kill 系统调用发送...

    操作系统进程控制实验报告.doc

    每个进程都有信号机制检验是否有信号到达,如果有,捕获信号后,根据系统默认处理或者用户自定义的防方法处理信号,当信号处理完后,在返回原来的程序继续执行。 6. 进程创建和控制: 进程创建和控制是操作系统中...

    进程与线程的生动形象理解

    ### 进程与线程的生动形象理解 #### 一、引言 进程与线程作为操作系统中的核心概念,对于理解和开发高效的软件至关重要。本文旨在通过一个生动的类比来帮助读者更好地理解进程与线程的基本概念及其差异,并进一步...

    进程三状态转化

    4. 阻塞(Blocked)/等待(Waiting):进程因等待某个事件(如I/O操作完成、信号量等)而无法继续执行,进入阻塞状态。当事件发生后,进程会被唤醒,重新变为就绪状态。 二、进程状态转换 1. 新建到就绪:进程创建...

    1操作系统实验五.docx

    操作系统实验报告实验五主要关注的是Linux环境下的进程同步与互斥机制,重点在于理解并应用信号量(Semaphore)以及P、V操作。实验目的是通过实际操作加深对进程同步互斥概念的理解,掌握信号量的使用方法和P、V操作...

    进程同步实验报告

    在操作系统中,进程同步是一个关键的概念,用于控制多个并发进程之间的协调与合作,确保它们能够正确、有序地访问共享资源,避免数据不一致和竞态条件。本实验报告旨在通过实际操作来深入理解和掌握进程同步的基本...

    操作系统实验三 进程调度算法实验

    与SCHED_FIFO类似,但RR策略会在时间片用完后将进程移到队列末尾,确保所有相同优先级的进程都能获得平等的执行机会。这样保证了实时性的同时也增加了调度的公平性。 实验的目标是通过编程和调试来实践这些调度算法...

    liunx信号概念及c信号函数的使用

    3. **信号量与互斥锁**:在多线程环境中,信号可能导致并发问题。合理使用信号量和互斥锁可以避免数据竞争。 4. **信号的顺序**:多个信号可能会同时到达,Linux没有保证信号的处理顺序。因此,设计程序时要考虑...

    C例子:使用信号量进行进程互斥

    该程序是我写的博客“一起talk C栗子吧(第一百零二回:C语言实例--使用信号量进行进程间同步与互斥三)”的配套程序,共享给大家使用

    用有名信号量和匿名信号量实现进程互斥

    它接受三个参数:指向信号量结构体的指针`sem`,一个标志`pshared`,以及一个初始值`value`。如果`pshared`为0,信号量将在进程内部使用;若非0,则为线程间使用。`value`是信号量的初始值,一般设置为1,表示只有一...

Global site tag (gtag.js) - Google Analytics