⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 appendix_c.htm

📁 UNIX环境下C编程的详细详细介绍
💻 HTM
📖 第 1 页 / 共 4 页
字号:
<p>而引起写失败,这种情况就调用dup将标准输出复制到另一个描述符,write</p>

<p>则使用 </p>

<p>新复制的文件描述符。 </p>

<p>8.2 我们可以通过程序C.6来说明这个问题。 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>static void f1(void), f2(void); </p>

<p>int </p>

<p>main(void) </p>

<p>{ </p>

<p>f1(); </p>

<p>f2(); </p>

<p>_exit(0); </p>

<p>} </p>

<p>static void </p>

<p>f1(void) </p>

<p>{ </p>

<p>pid_t pid; </p>

<p>if ( (pid = vfork()) &lt; 0) </p>

<p>err_sys(&quot;vfork error&quot;); </p>

<p>/* child and parent both return */ </p>

<p>} </p>

<p>static void </p>

<p>f2(void) </p>

<p>{ </p>

<p>char buf[1000]; /* automatic variables */ </p>

<p>int i; </p>

<p>for (i = 0; i &lt; sizeof(buf); i++) </p>

<p>buf[i] = 0; </p>

<p>} </p>

<p>程序C.6 错误使用vfork的例子 </p>

<p>图C.2 调用vfork时的栈帧 </p>

<p>当函数f1调用vfork时,父进程的堆栈指针就指向f1的栈帧,见图C.2。</p>

<p>vfork使得 </p>

<p>子进程先执行然后从f1返回,接着子进程调用f2并且覆盖了f1的堆栈区间,</p>

<p>在f2中 </p>

<p>子进程将自动变量buf的值置为0,即将堆栈中的1000个字节的值都置为0。</p>

<p>从f2返 </p>

<p>回后父进程继续执行调用_exit,这时堆栈中main以下的内容已经被f2修改</p>

<p>了,但 </p>

<p>是父进程仍然以为调用了vfork后从f1返回。返回信息虽然保存在堆栈中,但</p>

<p>是可 </p>

<p>能已经被子进程修改了。对这个例子,父进程继续执行的结果要依赖于实际的</p>

<p>Uni </p>

<p>x系统。(如:返回信息保存在堆栈的具体位置,修改动态变量时覆盖了哪些</p>

<p>信息等 </p>

<p>等。)通常的结果是一个core文件。 </p>

<p>8.3 程序8.7中我们先让父进程输出,但是当父进程输出完毕子进程要输出</p>

<p>时,我 </p>

<p>们要让父进程终止。是否父进程先终止或是子进程先执行输出要依赖于内核对</p>

<p>两个 </p>

<p>进程的调度。shell在父进程终止后会开始执行其它程序,这样也许仍会影响</p>

<p>子进 </p>

<p>程。要避免这种情况就是在子进程完成输出后才终止父进程。用下面的语句替</p>

<p>换程 </p>

<p>序中fork后面的代码。由于只有终止父进程才能开始下一个程序,所以不会</p>

<p>出现上 </p>

<p>面的情况。 </p>

<p>else if ( pid == 0 ) { </p>

<p>WAIT_PARENT( ) ; </p>

<p>/*parent goes first */ </p>

<p>charatatime ( &quot;output form child\n&quot; ) ; </p>

<p>TELL_PARENT ( getppid( )) ; </p>

<p>* tell parent we're done */ </p>

<p>} else { </p>

<p>charatatime ( &quot;output from parent\n&quot; ) ; </p>

<p>TELL_CHILD ( pid ) ; </p>

<p>* tell child we're done */ </p>

<p>WAIT_CHILD ( ) ; </p>

<p>/* wait for child to finish */ </p>

<p>8.4 对argv[2]打印的是相同的值(/home/stevens/bin/testinterp)。</p>

<p>原因是ex </p>

<p>eclp在结束时调用了execve,并且与直接调用execl的路径名相同。 </p>

<p>8.5不提供返回保存的用户ID的函数,我们必须在进程开始时保存有效的用户</p>

<p>ID。 </p>

<p>8.6 程序C.7创建了一个zombie。 </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>int </p>

<p>main(void) </p>

<p>{ </p>

<p>pid_t pid; </p>

<p>if ( (pid = fork()) &lt; 0) </p>

<p>err_sys(&quot;fork error&quot;); </p>

<p>else if (pid == 0) /* child */ </p>

<p>exit(0); </p>

<p>/* parent */ </p>

<p>sleep(4); </p>

<p>system(&quot;ps&quot;); </p>

<p>exit(0); </p>

<p>} </p>

<p>程序C.7 创建一个zombie并用ps查看其状态 </p>

<p>执行程序结果如下(ps(1)用&quot;Z&quot;表示Zombie): </p>

<p>$ a.out </p>

<p>PID TT STAT TIME COMMAND </p>

<p>5940 p3 S 0:00 a.out </p>

<p>5941 p3 Z 0:00 &lt;defunct&gt; the zombie </p>

<p>5942 p3 S 0:00 sh -c ps </p>

<p>5943 p3 R 0:00 ps </p>

<p> </p>

<p> </p>

<p>第九章 </p>

<p>9.1 因为init是login shell的父进程,当login shell终止时它收到</p>

<p>SIGCHLD信号 </p>

<p>量,所以init进程知道什么时候终端用户注销。 </p>

<p>网络login没有包含init,相应的logout项是由一个处理login并监测</p>

<p>logout的进 </p>

<p>程写的。(本例中为telnetd) </p>

<p> </p>

<p>第十章 </p>

<p>10.1 
当程序第一次接到我们发送给它的信号量就终止了。因为一捕捉到信号</p>

<p>量pa </p>

<p>use函数就返回。 </p>

<p>10.2 程序C.8实现了raise函数。 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;signal.h&gt; </p>

<p>#include &lt;unistd.h&gt; </p>

<p>int </p>

<p>raise(int signo) </p>

<p>{ </p>

<p>return( kill(getpid(), signo) ); </p>

<p>} </p>

<p>程序C.8 raise函数的实现 </p>

<p>10.3 见图C.3。 </p>

<p>图C.3 longjump前后的堆栈状态 </p>

<p>从sig_alrm通过longjmp返回main,有效地避免了继续执行sig_int。 </p>

<p>10.4 如果进程在调用alarm和setjmp之间被内核阻塞了,alarm时间走完之</p>

<p>后就调 </p>

<p>用信号量处理程序,然后调用longjmp。但是由于没有调用setjmp,所以没</p>

<p>有设置 </p>

<p>env_alrm缓存区。如果longjmp的跳转缓存区没有被setjmp初始化,则说明</p>

<p>没有定 </p>

<p>义longjmp的操作。 </p>

<p>10.5 参见&quot; Implementing Software Timers &quot; by Don Libers ( C</p>

<p>user Journa </p>

<p>l, Vol. 8, no. 11, Nov. 1990 )中的例子。 </p>

<p>10.7 如果仅仅调用_exit,则进程终止状态就不能表示该进程是由于</p>

<p>SIGABRT信号 </p>

<p>量而终止的。 </p>

<p>10.8 如果信号量是由其它用户的进程发出的,进程必须设置用户的ID为根或</p>

<p>者是 </p>

<p>接收进程的拥有者,否则kill不能执行。所以实际的用户ID为信号量的接收</p>

<p>者提供 </p>

<p>了更多的信息。 </p>

<p>10.10 对于本书中所用的系统,大约每60-90分钟增加一秒,这个误差是因</p>

<p>为每次 </p>

<p>调用sleep都要调度一将来的时间事件,但是由于CPU调度,有时我们并没有</p>

<p>在事件 </p>

<p>发生时被叫醒。另外一个原因是开始运行进程和调用sleep都需要一定的时</p>

<p>间。 </p>

<p>BSD中的cron每分钟都要取当前时间,它首先设置一个休眠周期,然后在下一</p>

<p>分钟 </p>

<p>开始时唤醒。大多数调用是sleep(60),偶尔有一个sleep(59)用于在下一</p>

<p>分钟同步 </p>

<p>。但是若在进程中花费了许多时间执行命令或者系统的负载重调度慢,这时休</p>

<p>眠值 </p>

<p>可能远小于60。 </p>

<p>10.11 在SVR4中,从来没有调用过SIGXFSZ的信号量处理程序,一旦文件的</p>

<p>大小达 </p>

<p>到1024时,write就返回24。 </p>

<p>在4.3+BSD中,文件大小达到1500字节时调用该信号处理程序,write返回</p>

<p>-1并且 </p>

<p>errno设置为EFBIG。 </p>

<p>SunOS4.1.2的情况与SVR4类似,但是调用了该信号量处理程序。 </p>

<p>System V在文件大小达到软资源限制时无错返回一个较小的数,而BSD判断</p>

<p>文件超 </p>

<p>出限制时错误返回,没有写任何数据。 </p>

<p>10.12 结果依赖于标准I/O库的实现--fwrite如何处理一个被中断的写。 </p>

<p>第十一章 </p>

<p>11.1 注意由于终端是非正规模式,所以要用换行符而不是回车终止reset命</p>

<p>令。 </p>

<p>11.2 它为128个字符建了一张表,根据用户的要求设置奇偶校验位。然后使</p>

<p>用8位 </p>

<p>I/O处理奇偶位的产生。 </p>

<p>11.3 在SVR4中运行stty -a,并且将标准输入重定向到运行vi的终端,结</p>

<p>果显示v </p>

<p>i设置MIN为1、TIME为1。reads等待至少敲入一个字符,但是该字符输入</p>

<p>后,只对 </p>

<p>后继的字符等待十分之一秒即返回。 </p>

<p>11.4 在SVR4中使用扩展的通用终端接口。参见AT&amp;T[1991]手册中的</p>

<p>temiox(7)。在 </p>

<p>4.3+BSD中使用c_cflag字段的CCTS_OFLOW和CRTS_IFLOW标志,参见图</p>

<p>11.3。 </p>

<p> </p>

<p>第十二章 </p>

<p>12.1 程序运行正常,不会发生ENOLCK的错误。第一次循环调用</p>

<p>writew_lock、wri </p>

<p>te和un_lock。调用un_lock后只保留了第一个字节的锁,第二次循环时,调</p>

<p>用wri </p>

<p>tew_lock使得新锁与第一个字节的锁合并,图C.4是第二次循环的结果。 
</p>

<p>图C.4 第二次循环后锁的状态 </p>

<p>每循环一次就扩展一个字节的锁,内核将这些锁合并后就只保持了一个锁,因</p>

<p>此 </p>

<p>符合锁结构的定义。 </p>

<p>12.2 在SVR4、4.3+BSD中,fd_set是只包含一个成员的结构,该成员为一个</p>

<p>长整型 </p>

<p>数组。数组中每一位对应于一个描述符。四个FD_宏通过开关或测试指定的位</p>

<p>来操 </p>

<p>纵这个数组。将之定义为一个包含数组的结构而不仅仅是一个数组的原因是:</p>

<p>通过 </p>

<p>C语言的赋值语句,可以使fd_set类型变量相互赋值。 </p>

<p>12.3 在SVR4、4.3+BSD中允许用户包含头文件&lt;sys/types.h&gt; 前定义常数</p>

<p>FD_SETS </p>

<p>IZE。例如下面的代码可以使fd_set数据类型包含2048个描述符。 </p>

<p>#define FD_SETSIZE 2048 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>12.4 下面的表列出了功能类似的函数。 </p>

<p>FD_ZERO sigemptyset </p>

<p>FD_SET sigaddset </p>

<p>FD_CLR sigdelset </p>

<p>FD_ISSET sigismember </p>

<p>没有与sigfillset对应的FD_XXX函数。对信号量来说,指向信号量集合的指</p>

<p>针是 </p>

<p>第一个参数,信号量数是第二个参数;对于描述符来说,描述符数是第一个参</p>

<p>数, </p>

<p>指向描述符集合的指针是第二个参数。 </p>

<p>12.5 
最多五种信息:数据,数据长度,控制信息,控制信息的长度和标志。</p>

<p>12.6 利用select实现的程序见C.9,利用poll实现的程序见C.10。 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;sys/time.h&gt; </p>

<p>#include &lt;stddef.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>void </p>

<p>sleep_us(unsigned int nusecs) </p>

<p>{ </p>

<p>struct timeval tval; </p>

<p>tval.tv_sec = nusecs / 1000000; </p>

<p>tval.tv_usec = nusecs % 1000000; </p>

<p>select(0, NULL, NULL, NULL, &amp;tval); </p>

<p>} </p>

<p>程序C.9 用select实现sleep_us函数 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;poll.h&gt; </p>

<p>#include &lt;stropts.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>void </p>

<p>sleep_us(unsigned int nusecs) </p>

<p>{ </p>

<p>struct pollfd dummy; </p>

<p>int timeout; </p>

<p>if ( (timeout = nusecs / 1000) &lt;= 0) </p>

<p>timeout = 1; </p>

<p>poll(&amp;dummy, 0, timeout); </p>

<p>} </p>

<p>程序C.10 用poll实现sleep_us函数 </p>

<p>BSD中的usleep(3)使用setitimer设置间隔计时器,并且执行8个系统调</p>

<p>用。它可 </p>

<p>以正确的和调用进程设置的其它计时器交互作用,而且即使捕捉到信号量也不</p>

<p>会被 </p>

<p>中断。 </p>

<p>12.7 不行。我们可以使TELL_WAIT创建一个临时文件,其中一个字节用作为</p>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -