UNIX环境高级编程学习之路(六)----进程控制

本文详细探讨了UNIX环境下进程控制的各个方面,包括进程标识、进程创建(fork函数)、进程终止(exit函数)、进程控制原语(wait、waitpid等)、解释器文件、用户ID与组ID的变更,以及进程会计、调度、时间等方面的知识。文章通过实例和代码演示了各种函数的使用,阐述了进程间通信和权限控制的重要概念,是深入理解UNIX进程控制的宝贵资料。
摘要由CSDN通过智能技术生成

对于UNIX环境编程,工作中经常会用到相关知识,作为学习UNIX环境编程的经典书籍–UNIX环境高级编程,是每个UNIX编程人员必看的经典书籍之一,为了将相关知识重新进行学习,以系统的整合所学知识,遂以博文形式作为总结。

1、概述
本节介绍UNIX系统的进程控制,包括创建新进程、执行程序和进程终止。还将说明进程属性的各种ID–实际、有效和保存的用户ID和组ID,以及他们如何受到进程控制原语的影响。还包括了解释器文件和system函数。

2、进程标识
每一个进程都有一个非负整形表示的唯一进程ID。因为进程ID标识符总是唯一的,常将其用来作其他标识符的一部分以保证其唯一性。例如,应用程序有时就把进程ID作为名字的一部分来创建一个唯一的文件名。
虽然是唯一的,但是进程ID是可以复用的。当一个进程终止后,其进程ID就成为复用的候选者。大多数UNIX系统实现延迟复用算法,使得赋予新建进程的ID不同于最近终止进程所使用的的ID。
系统中有一些专用进程。ID为0的通常是调度进程,常常被称为交换进程(swapper)。该进程是内核的一部分,他并不执行任何磁盘上的程序,因此也被称为系统进程。进程ID 1通常是init进程,在自举过程结束时由内核调用。该进程的程序文件在UNIX的早期版本中是/etc/init,在较新的版本中是/sbin/init。此进程负责在在自举内核后启动一个UNIX系统。init通常读取与系统有关的初始化文件(/etc/rc*文件或者/etc/inittab文件,以及在/etc/init.d中的文件),并将系统引导到一个状态(如多用户)。init进程决不会终止。他是一个普通的用户进程,但是以超级用户特权运行。
每个UNIX系统实现都有他自己的一套提供操作系统服务的内核进程。例如,在某些UNIX的虚拟存储实现中,进程ID 2是页守护进程(page daemon),此进程负责支持虚拟存储器系统的分页操作。

#include <unistd.h>
pid_t getpid(void);
                        返回值:调用进程的进程ID
pid_t getppid(void);
                        返回值:调用进程的父进程ID
uid_t getuid(void);
                        返回值:调用进程的实际用户ID
uid_t geteuid(void);
                        返回值:调用进程的有效用户ID
gid_t getgid(void);
                        返回值:调用进程的实际组ID
gid_t getegid(void);
                        返回值:调用进程的有效组ID

这些函数都没有出错返回。

3、函数fork

一个现有的进程可以调用fork函数创建一个新进程。

#include <unistd.h>
pid_t fork(void);
返回值:子进程返回0,父进程返回子进程ID,若出错,返回-1

由fork创建的新进程被称为子进程(child process)。fork函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值是新建子进程的进程ID。
子进程和父进程继续执行fork调用之后IDE指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。注意,这是子进程所拥有的副本。父进程和子进程并不共享这些存储空间部分。父进程和子进程共享正文段。 由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、站和堆的完全副本。作为替代,使用了写时复制(Copy-On_Write,COW)技术。这些区域由父进程和子进程共享,而且内核将他们的访问权限改变为只读。如果父进程和子进程中任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储系统中的一“页”。
以下程序演示了fork函数,从中看到子进程对变量所做的改变并不影响父进程中该变量的值。

#include <stdio.h>
#include <unistd.h>

int global_var = 6;
char buf[] = "a write to stdout\n";

int main(void)
{
    int var;
    pid_t pid;

    var = 88;
    if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
    {
            printf("write error!\n");
    }
    printf("before fork\n");

    if ((pid = fork()) < 0)
    {
        printf("fork error!\n");
    } else if (pid == 0) {
        global_var++;
        var++;
        printf("ppid = %ld\n", (long)getppid());
        } else {
        sleep(2);
        }

    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), global_var, var);
    return 0;
}

执行此程序得到:

image
image

如上两图所示程序中fork与I/O函数之间的交互关系。write函数是不带缓冲的。因为在fork之前调用write,所以其数据写到标准输出一次。但是,标准I/O库是带缓冲的。当以交互方式运行该程序时,只得到该printf输出的行一次,其原因是标准输出缓冲区由换行符冲洗。但是当将标准输出重定向到一个文件时,却得到printf输出行两次。其原因是,==在fork之前调用了printf一次,但当调用fork时,该行数据仍在缓冲区中,然后再将父进程数据空间复制到子进程中时,该缓冲区数据也被复制到子进程中,此时父进程和子进程各有了带行内容的缓冲区。在exit之前的第二个printf将其数据追加到已有的缓冲区中==。当每个进程终止时,其缓冲区中的内容都被写到相应文件中。

freopen函数在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。若该流已经定向,则使用freopen清除该定向。此函数一般用于将一个指定文件打开为一个预定义的流:标准输入、标准输出和标准错误

进行重定向:

stdout = freopen("./tmp.out", "w", stdout);
  • 文件共享
    在重定向父进程的标准输出时,子进程的标准输出也被重定向。实际上,fork的一个特性是父进程的所有打开文件文件描述符都被复制到子进程中。我呢说“复制”是因为对每个文件描述符来说,就好像执行了dup函数。父进程和子进程每个相同的打开文件描述符共共享一个文件表项。
    考虑下列情况,一个进程具有3个不同的打开文件,他们是标准输入、标准输出和标准错误。在从fork返回时,有如下所示结构:
    image

重要一点是,父进程和子进程共享同一文件偏移量。考虑下述情况:一个进程fork了一个子进程,然后等待子进程终止。假定,作为普通处理的一部分,父进程和子进程都向标准输出进行写操作。如果父进程的标准输出已经重定向,那么子进程写到该标准输出时,它将更新与父进程共享的该文件的偏移量。当前例子中,当父进程等待子进程时,子进程写到标准输出:而在子进程终止后,父进程也写到标准输出上,并知道其输出会追加在子进程所写数据之后。如果子进程和父进程不共享同一文件偏移量,要实现这种形式的交互就困难的多,可能需要父进程显示的动作。
如果父进程和子进程写同一描述符指向的文件,但又没有任何形式的同步(如父进程等待子进程),那么他们的输出就会相互混合。虽然这种情况是可能发生的,但并不是常用的操作模式。
在fork之后处理文件描述符有以下两种常见的情况。
(1)父进程等待子进程的完成。在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读、写操作的任一共享描述符的文件偏移量已经做了相应的更新。
(2)父进程和子进程各自执行不同的程序段。在这种情况下,在fork之后,父进程和子进程各自关闭他们不需使用的文件描述符,这样就不会干扰对方使用的文件描述符。这种方法是网络服务进程经常使用的。
父进程和子进程之间的区别如下。
* fork的返回值不同。
* 进程ID不同。
* 这两个进程的父进程ID不同:子进程的父进程ID是创建他的进程的ID,而父进程的父进程ID则不变。
* 子进程的tms_utime、tms_stime和tms_ustime的值设置为0。
* 子进程不继承父进程设置的文件锁。
* 子进程的未处理闹钟被清除。
* 子进程的未处理信号集设置为空集。

使用fork失败的原因主要有两个:(a)系统中已经有了太多的进程,(b)该实际用户ID的进程总数超过了系统限制。
fork有以下两种用法。
(1)一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段。这在网络服务进程中是常见的–父进程等待客户端服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求。
(2)一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。
有些操作系统将第二种用法的两个操作组合成一个操作,称为spawn。UNIX系统将这两个操作分开,因为在很多场合需要单独使用fork,其后并不更随exec。另外,将这两个操作分开,使得子进程在fork和exec之间可以更改自己的属性,如I/O重定向,用户ID、信号安排等。

4、函数vfork
vfork函数的调用序列和返回值与fork相同,但两者的语义不同。
vfork函数用于创建一个新进程,而该进程的目的是exec一个新程序。vfork与fork一样都创建一个子进程,但是他并不将父进程的地址空间完全复制到子进程中因为子进程会立即调用exec(或exit),于是也就不会引用该地址空间。不过在子进程调用exec或exit之前,他在父进程的空间中进行。这种优化工作方式在某些UNIX系统的实现中提高了效率,但如果子进程修改数据、进行函数调用、或者没有调用exec或exit就返回都可能带来未知的结果。
vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。当子进程调用这两个函数中的任意一个时,父进程会恢复运行。
以下程序是用vfork代替fork后运行的代码。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int global_var = 6;

int main(void)
{
        int var;
        pid_t pid;
        var = 88;

        printf("before vfork\n");
        if ((pid = vfork()) < 0) {
                printf("vfork error!");
        } else if (pid == 0) {
                global_var++;
                var++;
                _exit(0);
        }

        printf("pid = %ld,glob = %d,var = %d\n", (long)getpid(), global_var, var);

        exit(0);
}

执行后结果为:
image

5、函数exit
进程有5种正常终止以及3种异常终止方式。5种正常终止方式如下:
(1)、在main函数内执行return语句。这等效于调用exit。
(2)、调用exit函数。其包括调用各终止处理程序(终止处理程序在调用atexit函数时登记),然后关闭所有标准I/O流等。因为ISO C并不处理文件描述符、多进程(父进程和子进程)以及作业控制,所以这一定义对于UNIX系统而言是不完整的。
(3)、调用_exit或_Exit函数。ISO C定义_Exit,其目的是为进程提供一种无需运行终止处理程序或信号处理程序而终止的方法。对标准I/O是否进行冲洗,这取决于实现。在UNIX系统中,_Exit和_exit是同义的,并不冲洗标准I/O流。_exit函数由exit调用。他处理UNIX系统的特定的细节。
(4)、进程的最后一个线程在其启动例程中执行return语句。但是,该线程的返回值不用做进程的返回值。当最后一个线程从启动例程返回时,该进程以终止状态0返回。
(5)、进程的最后一个线程调用pthread_exit函数。在这种情况下,进程终止状态总是0,这与传递给pthread_exit的参数无关。
3种异常终止具体如下:
(1)调用abort。它产生SIGABR信号;
(2)当进程接收到某些信号时。信号可由进程自身、其它进程或内核产生。例如,若进程引用地址空间之外的存储单元、或者除以0,内核就会为该进程产生相应的信号。
(3)最后一个线程对“取消”请求作出响应。默认情况下,“取消”以延迟方式发生:一个线程要求取消另一个线程,若干时间之后,目标线程终止。
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放他所使用的存储器等。
对于上述任意一种终止情形,我们都希望终止进程能够通知他是如何终止的。对于3个终止函数(exit、_exit和_Exit),实现这一点的方法是,将其退出状态作为参数传递给函数。在异常终止情况,内核产生一个指示其异常终止的原因的终止状态。在任一一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。
在说明fork函数时,子进程是在父进程调用fork后生成的。子进程将其终止状态返回给父进程。但是如果父进程在子进程之前终止,该如何?回答是:对于父进程已经终止的所有进程,他们的父进程都改变为init进程。我们称这些进程由init进程收养。其操作过程大概是:在一个进程终止时,内核诸葛检查所有活动进程,以判断他是否是正要终止进程的子进程,如果是,则该进程的父进程的ID更改为1(init进程的ID)。这种处理方法保证了每一个进程都有一个父进程。
如果子进程在父进程之前终止,那么父进程如何在做相应检查时得到子进程的终止状态呢?如果子进程完全消失了,父进程在最终准备好检查子进程是否终止时是无法获得他的终止状态的。内核为每个终止子进程保存了一定量的信息。所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包括进程ID、该进程的终止状态以及该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储区,关闭其所有打开文件。在UNIX术语中,一切已经终止、但是其父进程尚未对其进行善后处理的进程被称为僵尸进程。==如果一个长期运行的程序,它fork了很多子进程,那么除非父进程取得子进程的终止状态,不然子这些进程终止后就会变成僵尸进程==。
一个由init进程收养的进程最终终止时会发生什么?他会不会变成一个僵尸进程,不会的,因为init被编写成无论何时只要有一个子进程终止,init就会调用一个wait函数取得其终止状态。这样也就防止了在系统中塞满了僵尸进程。当提及“一个init的子进程”时,这指的是init直接产生的进程,也可能是其父进程已经终止,由init收养的过程。

6、函数wait和waitpid
当一个进程正常或者异常终止时,

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目 录 译者序 译者简介 前言 第1章 UNIX基础知识 1 1.1 引言 1 1.2 登录 1 1.2.1 登录名 1 1.2.2 shell 1 1.3 文件和目录 2 1.3.1 文件系统 2 1.3.2 文件名 2 1.3.3 路径名 2 1.3.4 工作目录 4 1.3.5 起始目录 4 1.4 输入和输出 5 1.4.1 文件描述符 5 1.4.2 标准输入、标准输出和标准 出错 5 1.4.3 不用缓存的I/O 5 1.4.4 标准I/O 6 1.5 程序和进程 7 1.5.1 程序 7 1.5.2 进程进程ID 7 1.5.3 进程控制 7 1.6 ANSI C 9 1.6.1 函数原型 9 1.6.2 类属指针 9 1.6.3 原始系统数据类型 10 1.7 出错处理 10 1.8 用户标识 11 1.8.1 用户ID 11 1.8.2 组ID 12 1.8.3 添加组ID 12 1.9 信号 12 1.10 UNIX时间值 14 1.11 系统调用和库函数 14 1.12 小结 16 习题 16 第2章 UNIX标准化及实现 17 2.1 引言 17 2.2 UNIX标准化 17 2.2.1 ANSI C 17 2.2.2 IEEE POSIX 18 2.2.3 X/Open XPG3 19 2.2.4 FIPS 19 2.3 UNIX实现 19 2.3.1 SVR4 20 2.3.2 4.3+BSD 20 2.4 标准和实现的关系 21 2.5 限制 21 2.5.1 ANSI C限制 22 2.5.2 POSIX限制 22 2.5.3 XPG3限制 24 2.5.4 sysconf、pathconf 和fpathconf 函数 24 2.5.5 FIPS 151-1要求 28 2.5.6 限制总结 28 2.5.7 未确定的运行时间限制 29 2.6 功能测试宏 32 2.7 基本系统数据类型 32 2.8 标准之间的冲突 33 2.9 小结 34 习题 34 第3章 文件I/O 35 3.1 引言 35 3.2 文件描述符 35 3.3 open函数 35 3.4 creat函数 37 3.5 close函数 37 3.6 lseek函数 38 3.7 read函数 40 3.8 write函数 41 3.9 I/O的效率 41 3.10 文件共享 42 3.11 原子操作 45 3.11.1 添加至一个文件 45 3.11.2 创建一个文件 45 3.12 dup和dup2函数 46 3.13 fcntl函数 47 3.14 ioctl函数 50 3.15 /dev/fd 51 3.16 小结 52 习题 52 第4章 文件和目录 54 4.1 引言 54 4.2 stat, fstat和lstat函数 54 4.3 文件类型 55 4.4 设置-用户-ID和设置-组-ID 57 4.5 文件存取许可权 58 4.6 新文件和目录的所有权 60 4.7 access函数 60 4.8 umask函数 62 4.9 chmod和fchmod函数 63 4.10 粘住位 65 4.11 chown, fchown和 lchown函数 66 4.12 文件长度 67 4.13 文件截短 68 4.14 文件系统 69 4.15 link, unlink, remove和rename 函数 71 4.16 符号连接 73 4.17 symlink 和readlink函数 76 4.18 文件的时间 76 4.19 utime函数 78 4.20 mkdir和rmdir函数 79 4.21 读目录 80 4.22 chdir, fchdir和getcwd函数 84 4.23 特殊设备文件 86 4.24 sync和fsync函数 87 4.25 文件存取许可权位小结 88 4.26 小结 89 习题 89 第5章 标准I/O库 91 5.1 引言 91 5.2 流和FILE对象 91 5.3 标准输入、标准输出和标准出错 91 5.4 缓存 91 5.5 打开流 94 5.6 读和写流 96 5.6.1 输入函数 96 5.6.2 输出函数 97 5.7 每次一行I/O 98 5.8 标准I/O的效率 99 5.9 二进制I/O 100 5.10 定位流 102 5.11 格式化I/O 103 5.11.1 格式化输出 103 5.11.2 格式化输入 103 5.12 实现细节 104 5.13 临时文件 105 5.14 标准I/O的替代软件 108 5.15 小结 108 习题 108 第6章 系统数据文件和信息 110 6.1 引言 110 6.2 口令文件 110 6.3 阴影口令 112 6.4 组文件 113 6.5 添加组ID 114 6.6 其他数据文件 115 6.7 登录会计 116 6.8 系统标识 116 6.9 时间和日期例程 117 6.10 小结 121 习题 121 第7章 UNIX进程环境 122 7.1 引言 122 7.2 main 函数 122 7.3 进程终止 122 7.3.1 exit和_exit函数 122 7.3.2 atexit函数 124 7.4 命令行参数 125 7.5 环境表 126 7.6 C程序的存储空间布局 126 7.7 共享库 127 7.8 存储器分配 128 7.9 环境变量 130 7.10 setjmp 和longjmp函数 132 7.10.1 自动、寄存器和易失变量 134 7.10.2 自动变量的潜在问题 136 7.11 getrlimit 和setrlimit函数 136 7.12 小结 139 习题 140 第8章 进程控制 141 8.1 引言 141 8.2 进程标识 141 8.3 fork函数 142 8.4 vfork 函数 145 8.5 exit函数 147 8.6 wait和waitpid函数 148 8.7 wait3和wait4函数 152 8.8 竞态条件 153 8.9 exec函数 156 8.10 更改用户ID和组ID 160 8.10.1 setreuid 和setregid函数 162 8.10.2 seteuid和 setegid函数 163 8.10.3 组ID 163 8.11 解释器文件 164 8.12 system函数 167 8.13 进程会计 171 8.14 用户标识 175 8.15 进程时间 176 8.16 小结 178 习题 178 第9章 进程关系 180 9.1 引言 180 9.2 终端登录 180 9.2.1 4.3+BSD终端登录 180 9.2.2 SVR4终端登录 182 9.3 网络登录 182 9.3.1 4.3+BSD网络登录 182 9.3.2 SVR4网络登录 183 9.4 进程组 183 9.5 对话期 184 9.6 控制终端 185 9.7 tcgetpgrp 和tcsetpgrp函数 187 9.8 作业控制 187 9.9 shell执行程序 189 9.10 孤儿进程组 193 9.11 4.3+BSD实现 195 9.12 小结 197 习题 197 第10章 信号 198 10.1 引言 198 10.2 信号的概念 198 10.3 signal函数 203 10.3.1 程序起动 205 10.3.2 进程创建 206 10.4 不可靠的信号 206 10.5 中断的系统调用 207 10.6 可再入函数 209 10.7 SIGCLD语义 211 10.8 可靠信号术语和语义 213 10.9 kill和raise函数 213 10.10 alarm和pause函数 214 10.11 信号集 219 10.12 sigprocmask 函数 220 10.13 sigpending函数 222 10.14 sigaction函数 223 10.15 sigsetjmp 和siglongjmp函数 226 10.16 sigsuspend函数 229 10.17 abort函数 234 10.18 system函数 235 10.19 sleep函数 240 10.20 作业控制信号 241 10.21 其他特征 243 10.21.1 信号名字 243 10.21.2 SVR4信号处理程序的附 加参数 244 10.21.3 4.3+BSD信号处理程序的附 加参数 244 10.22 小结 244 习题 244 第11章 终端I/O 246 11.1 引言 246 11.2 综述 246 11.3 特殊输入字符 250 11.4 获得和设置终端属性 254 11.5 终端选择标志 254 11.6 stty命令 258 11.7 波特率函数 259 11.8 行控制函数 260 11.9 终端标识 260 11.10 规范方式 263 11.11 非规范方式 266 11.12 终端的窗口大小 270 11.13 termcap, terminfo和 curses 271 11.14 小结 272 习题 272 第12章 高级I/O 273 12.1 引言 273 12.2 非阻塞I/O 273 12.3 记录锁 275 12.3.1 历史 276 12.3.2 fcntl记录锁 276 12.3.3 锁的隐含继承和释放 280 12.3.4 4.3+BSD的实现 281 12.3.5 建议性锁和强制性锁 284 12.4 流 288 12.4.1 流消息 289 12.4.2 putmsg和putpmsg函数 290 12.4.3 流ioctl操作 291 12.4.4 write至流设备 294 12.4.5 写方式 294 12.4.6 getmsg和getpmsg函数 294 12.4.7 读方式 295 12.5 I/O多路转接 296 12.5.1 select函数 298 12.5.2 poll函数 301 12.6 异步I/O 303 12.6.1 SVR4 303 12.6.2 4.3+BSD 303 12.7 readv和writev函数 304 12.8 readn和writen函数 306 12.9 存储映射I/O 307 12.10 小结 311 习题 311 第13章 精灵进程 312 13.1 引言 312 13.2 精灵进程的特征 312 13.3 编程规则 313 13.4 出错记录 314 13.4.1 SVR4流log驱动程序 315 13.4.2 4.3+BSD syslog设施 316 13.5 客户机-服务器模型 319 13.6 小结 319 习题 319 第14章 进程间通信 320 14.1 引言 320 14.2 管道 320 14.3 popen和pclose函数 325 14.4 协同进程 330 14.5 FIFO 333 14.6 系统V IPC 335 14.6.1 标识符和关键字 336 14.6.2 许可权结构 337 14.6.3 结构限制 337 14.6.4 优点和缺点 337 14.7 消息队列 338 14.8 信号量 342 14.9 共享存储 346 14.10 客户机-服务器属性 351 14.11 小结 353 习题 353 第15章 高级进程间通信 355 15.1 引言 355 15.2 流管道 355 15.3 传送文件描述符 358 15.3.1 SVR4 360 15.3.2 4.3BSD 361 15.3.3 4.3+BSD 364 15.4 open服务器第1版 366 15.5 客户机-服务器连接函数 371 15.5.1 SVR4 372 15.5.2 4.3+BSD 375 15.6 open服务器第2版 378 15.7 小结 385 习题 385 第16章 数据库函数库 386 16.1 引言 386 16.2 历史 386 16.3 函数库 386 16.4 实现概述 388 16.5 集中式或非集中式 390 16.6 并发 391 16.6.1 粗锁 391 16.6.2 细锁 391 16.7 源码 392 16.8 性能 409 16.8.1 单进程的结果 410 16.8.2 多进程的结果 410 16.9 小结 412 习题 412 第17章 与PostScript打印机通信 413 17.1 引言 413 17.2 PostScript通信机制 413 17.3 假脱机打印 415 17.4 源码 417 17.5 小结 434 习题 434 第18章 调制解调器拨号器 435 18.1 引言 435 18.2 历史 435 18.3 程序设计 436 18.4 数据文件 437 18.5 服务器设计 439 18.6 服务器源码 439 18.7 客户机设计 463 18.7.1 终端行规程 463 18.7.2 一个进程还是两个进程 464 18.8 客户机源码 465 18.9 小结 474 习题 474 第19章 伪终端 476 19.1 引言 476 19.2 概述 476 19.2.1 网络登录服务器 477 19.2.2 script程序 478 19.2.3 expect程序 479 19.2.4 运行协同进程 479 19.2.5 观看长时间运行程序的输出 479 19.3 打开伪终端设备 480 19.3.1 SVR4 481 19.3.2 4.3+BSD 482 19.4 pty_fork函数 484 19.5 pty程序 486 19.6 使用pty程序 489 19.6.1 utmp文件 489 19.6.2 作业控制交互 489 19.6.3 检查长时间运行程序的输出 491 19.6.4 script程序 491 19.6.5 运行协同进程 492 19.6.6 用非交互模式驱动交互式 程序 492 19.7 其他特性 494 19.7.1 打包模式 494 19.7.2 远程模式 494 19.7.3 窗口大小变化 495 19.7.4 信号发生 495 19.8 小结 495 习题 495 附录A 函数原型 497 附录B 其他源代码 512 附录C 习题答案 518 参考书目 536
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值