《UNIX环境高级编程》章节试读

当前位置:首页 > 网络编程 > > UNIX环境高级编程章节试读

出版社:人民邮电出版社
出版日期:2006-2
ISBN:9787115144843
作者:W.Richard Stevens Stephen A.Rago,Stephen A. Rago
页数:927页

《UNIX环境高级编程》的笔记-第228页

Race Conditions
如果一个子进程child,在运行期内getppid,如果这时它的父进程还活着的话(没有exit,没有return或其他可能被kill的方式),那就获得它父进程的pid。
如果此时父进程已经终止,getppid会得到1,也就是init的pid。
有时通过sleep也不能保证它的父进程已经终止(比如系统负载比较重的情况),比如某进程已经从sleep醒过来,它的父进程却还没运行。这种会很难debug
如果一个进程希望等到它父进程终止,可以用polling的方式,如
while(getppid() != 1)
sleep(1);
但这种缺点显而易见。

《UNIX环境高级编程》的笔记-第496页

Personal conprehension
>>>> IPC Identifiers
Each IPC Object has a unique identifier associated with it. This identifier is used within the kernel to uniquely identify an IPC Object.
The uniqueness of an identifier is relevant to the type of object in question. Assume a numeric identifier of "12345", while there can never be two message queues with this same identifier, there exists the distinct possibility of a message queue and, say, a shared memory segment, which have the same numeric identifier.
>>>> IPC Keys
To obtain a unique ID, a key must be used. The key must be mutually agreed upon by both client and server processes. This represents the first step in constructing a client/server framework for an application.
Let's see an example :
When you use a telephone to call someone, you must know their number. In addition, the phone company must know how to relay your outgoing call to its final destination. Once the other party responds by answering the telephone call, the connection is made.
In IPC, the "telephone" correllates directly with the type of object being used, which is recognized through IPC Identifier. The "phone company", or routing method, can be directly associated with an IPC Key.
>>>> ipcs command
used to obtain the status of all System V IPC objects

《UNIX环境高级编程》的笔记-第560页

存疑:
inet_ntop(AF_INET,
&(((struct sockaddr_in *)(curr->ai_addr))->sin_addr),
ipstr, 16);
第二个参数为什么要写成那样?
貌似在UNP 卷一里面会有答案,先放着,等到看UNP的时候在回来解答。

《UNIX环境高级编程》的笔记-第576页

(关于getaddrinfo()函数,从网上找到一些资料)
这个函数产生原因: 以前版本里面需要套接字编程,要得到sockaddr 类型: 必须使用两种函数 getserverbyname (一类的函数) 和 gethostent (一类的函数), 然后构建sockaddr_in 结构,在强制类型转换得到 sockaddr结构。 但是 getaddrinfo有了这个函数,这里过程就不用了,直接调用,就能得到 sockaddr 。 简化了编程过程。

《UNIX环境高级编程》的笔记-第475页

select()成功返回时,每组set都被修改以使它只包含准备好I/O的文件描述符。例如,假设有两个文件描述符,值分别是7和9,被放在 readfds中。当select()返回时,如果7仍然在set中,则这个文件描述符已经准备好被读取而不会阻塞。如果9已经不在set中,则读取它将 可能会阻塞(我说可能是因为数据可能正好在select返回后就可用,这种情况下,下一次调用select()将返回文件描述符准备好读取)。

《UNIX环境高级编程》的笔记-第225页

int
main(void)
{
pid_t pid;
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { /* first child */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0)
exit(0); /* parent from second fork == first child */
/*
* We're the second child; our parent becomes init as soon
* as our real parent calls exit() in the statement above.
* Here's where we'd continue executing, knowing that when
* we're done, init will reap our status.
*/
sleep(2);
printf("second child, parent pid = %d\n", getppid());
exit(0);
}
if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
err_sys("waitpid error");
/*
* We're the parent (the original process); we continue executing,
* knowing that we're not the parent of the second child.
*/
exit(0);
}这段代码说的就是:老爷子想要个孙子又不想负责任。于是强要儿子生了个孙子,转而就把儿子杀了。然后名正言顺地把孙子,哦不,这个不认识的可怜孤儿过继给了大户人家init。

《UNIX环境高级编程》的笔记-Ch 8 Process Control - Ch 8 Process Control

Some special processes:
PID
0 swapper 系统进程,scheduler process
1 init 用户进程,never dies。以super特权级运行始终
2 pagedaemon 支持虚拟内存的页式调度
关于fork,
在父进程里会返回child 的PID
在子进程里返回0
至于为什么子进程的待遇怎么差(返回这样一个“没什么用”的值)。APUE给出的解释是首先UNIX本身就提供了getppid()这样一个函数可以直接供子进程调用来获知他老爸是谁;而父进程无法通过getcpid()来获知这次他这次种下的种子产生了哪个孩子(也根本就没有getcpid()这个函数)。UNIX里父进程是不管他孩子的死活的。
用fork()产生子进程时,采用的是写时复制技术(copy-on-write,COW)。APUE描述这里蛮简短,就说很多region,比如data,stack和heap等,对于父进程和子进程都是共享的,kernel会把这些region的保护级别都定为只读。当有其他进程想要改变这些区域时,kernel把它想改变的region复制一份出来,通常都是复制一页。通过这种机制来保证最小的进程复制开销。
百度百科上的一段:
Scott Meyers在《More Effective C++》 中举了个例子,在你还在上学的时候,你的父母要你不要看电视,而去复习功课,于是你把自己关在房间里,做出一副正在复习功课的样子,其实你在干着别的诸如给班上的某位女生写情书之类的事,而一旦你的父母出来在你房间要检查你是否在复习时,你才真正捡起课本看书。这就是“拖延战术”,直到 你非要做的时候才去做。
Parent and the child share a file table entry for every open descriptor.同步,重定向等实现都要求parent和child必须share same file offset。
通常会有如下2种方式来处理fork后的descriptor问题。
1. 父进程等待子进程完成它的动作。这种情况父进程可以无视descriptor,子进程完成之后,所有share的descriptor和file offset都会自动更新到父进程那边。
2. 父进程和子进程花开两朵,各表一枝。这种情况下在fork之后,父进程和子进程会关闭它们不需要的那些descriptor,这样做到互不干扰。在网络服务器比较常见。
vfork与fork的区别:vfork不会把父进程的地址空间完全复制给子进程,因为子进程创建之后就会exec或者exit。同时vfork保证子进程先于父进程运行,直到子进程exec或者exit后,父进程再继续运行。

《UNIX环境高级编程》的笔记-第142页

gets(char *buf)因为不能设置buf的大小,所以可能会造成缓冲溢出。
所以就写了这样的一个小测试程序,编译器是gcc 3.4.2#include <stdio.h>
void LineATimeTest()
{
char buf[10];
printf("%s",gets(buf));
}
int main()
{
LineATimeTest();
return 0;
}
buf的大小设为10的时候,经过几次实验,当一行的字符大于24时,才会报错。输入23个字符一下(已经大于buf size)都可以正常显示
buf的大小设为2的时候,则只能输入一个字符。输入2个字符以上就报错。
呵呵,有点想不通

《UNIX环境高级编程》的笔记-第220页

wait&waitpid
进程结束,无论是否正常中止,内核都会给它的父进程发送一个SIGCHILD的通知。(虽然这个老爸一点都不计划生育也不在乎孩子,但是孩子死了总得通知他一下吧)。显然这个通知是异步的,某天老爸下班回家可能就在邮箱里看到它若干孩子的死亡证明了。父进程是比较不负责任的,所以它可能随手把这个死亡证明扔了(ignore it),或者如果他比较爱那个孩子的话,就给它办个葬礼(signal handler)。呐,都说了这个老爸是不负责任的,所以default方式就是扔了。
作为一个进程,它调用wait或者waitpid的话会
1.block。 如果它所有的子进程都在运行(这里是否应该理解为这些子进程没有死掉)
2.带着某个子进程的中止状态立即返回。如果有一个子进程已经中止并等待它父进程来存取它的中止状态。
3,如果它已经没有子进程了,就以错误退出。
wait会使调用者block,直到它有某个子进程中止为止。而waitpid的第三个参数可使调用者不中止。
下面的部分是转来的,源网址http://doc.linuxpk.com/53457.html
对于进程的一生可以用一些形象的比喻作一个小小的总结:
随着一句fork,一个新进程呱呱落地,但它这时只是老进程的一个克隆。
然后随着exec,新进程脱胎换骨,离家独立,开始了为人民服务的职业生涯。
人有生老病死,进程也一样,它可以是自然死亡,即运行到main函数的最后一个”}”,从容地离我们而去;也可以是自杀,自杀有2种方式,一种是调用 exit函数,一种是在main函数内使用return,无论哪一种方式,它都可以留下遗书,放在返回值里保留下来;它还甚至能可被谋杀,被其它进程通过另外一些方式结束他的生命。
进程死掉以后,会留下一具僵尸,wait和waitpid充当了殓尸工,把僵尸推去火化,使其最终归于无形。

在linux中wait系统调用一文中介绍了其中的一个殓尸工wait, 下面介绍另一个waitpid,这个貌似复杂些。

waitpid函数原型:

#include<sys/types.h>/* 提供类型pid_t的定义 */
#include<sys/wait.h>
pid_twaitpid(pid_tpid,int*status,intoptions);
从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下这两个参数:

pid

从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。

1. pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
2. pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
3. pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
4. pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

options

options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用”|”运算符把它们连接起来使用,比如:

ret=waitpid(-1,NULL,WNOHANG|WUNTRACED);
如果我们不想使用它们,也可以把options设为0,如:

ret=waitpid(-1,NULL,0);
如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

而WUNTRACED参数,用于跟踪调试,极少用到,就不说了。

查看linux源代码 unistd.h 我们会发现,其实 wait 就是经过包装的 waitpid:

staticinlinepid_twait(int*wait_stat)
{
returnwaitpid(-1,wait_stat,0);
}
waitpid的返回值比wait稍微复杂一些,一共有3种情况:

1. 当正常返回的时候,waitpid返回收集到的子进程的进程ID;
2. 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;

下面看一个简单的例子:

下载:waitpid.c
/* waitpid.c */
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
pid_t pc,pr;
pc=fork();
if (pc<0)/* fork错误*/
{
printf("fork error\n");
exit(1);
}
else if(pc==0)/*在子进程中*/
{
sleep(10);
exit(0);
}
else
{
do {/* 使用了WNOHANG参数,waitpid不会在这里等待 */
pr=waitpid(pc,NULL,WNOHANG);
if (pr==0)
{
printf("No child exit\n");
sleep(1);
}
}while (pr==0);
if (pr==pc)
printf("successfully get child %d\n",pr);
else
printf("wait child error\n");
}
return 0;
}
编译并运行:

$ gcc -o waitpid waitpid.c
$ ./waitpid
No child exit
No child exit
No child exit
No child exit
No child exit
No child exit
No child exit
No child exit
No child exit
No child exit
successfully get child 4607
父进程经过10次失败的尝试之后,终于收集到了退出的子进程。父进程和子进程分别睡眠了10秒钟和1秒钟,代表它们分别作了10秒钟和1秒钟的工作。父子进程都有工作要做,父进程利用工作的简短间歇察看子进程的是否退出,如退出就收集它。

《UNIX环境高级编程》的笔记-第1页

补充一段代码 充分理解UNIX FIFO
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/stat.h>
#define FIFO_FILE "MYFIFO"
int main(void)
{
FILE *fp;
char readbuf[80];
/* Create the FIFO if it does not exist */
umask(0);
mknod(FIFO_FILE, S_IFIFO|0666, 0);
while(1)
{
fp = fopen(FIFO_FILE, "r");
fgets(readbuf, 80, fp);
printf("Received string: %s\n", readbuf);
fclose(fp);
}
return(0);
}
/*
Since a FIFO blocks by default, run the server in the background after you compile it:
$ fifoserver&
Consider the following simple client frontend to our server:
*/
#include <stdio.h>
#include <stdlib.h>
#define FIFO_FILE "MYFIFO"
int main(int argc, char *argv[])
{
FILE *fp;
if ( argc != 2 ) {
printf("USAGE: fifoclient [string]\n");
exit(1);
}
if((fp = fopen(FIFO_FILE, "w")) == NULL) {
perror("fopen");
exit(1);
}
fputs(argv[1], fp);
fclose(fp);
return(0);
}

《UNIX环境高级编程》的笔记-第382页 - Reader-Writer locks

以前总觉得读共享数据不需要加锁,所以就觉得读写锁完全没必要啊。
后来发现,读的时候也要加锁。因为读被写打断可能会造成错误。
豆瓣上不能找到一群正在看一本书的人一起讨论啊。失望。。。


 UNIX环境高级编程下载 更多精彩书评


 

外国儿童文学,篆刻,百科,生物科学,科普,初中通用,育儿亲子,美容护肤PDF图书下载,。 零度图书网 

零度图书网 @ 2024