前几天周末,加上老板催着改专利,耽误了一些时间,继续开始。
首先,实例“图1-4”,copy standard input to standard output(ps:越来越发现看中文版的别扭了)。
<span style="font-family:SimHei;font-size:18px;">n = read(STDIN_FILENO, buf, BUFFSIZE)</span>
char buf[BUFFSIZE]就不用说了,但是这个BUFFSIZE的取值会影响程序的效率,这个以后会看到。主要看这个STDIN_FILENO是个什么东西。无疑,它是一个file descriptor(文件描述符)。按惯例,每当运行一个新程序时,shell为其打开3个文件描述符:standard input,standard output和standard error。不做特殊处理的话,三个文件描述符都指向终端,如$ls。也可以使其中任意一个重定向到某个文件,比如
<span style="font-family:SimHei;font-size:18px;">ls > file.list</span>
它的意思是,将ls的输出重定向到file.list输出。
至于STDIN_FILENO file descriptor,像大多数文件描述符一样,是一个小的非负整数,是内核用来标记某进程正在访问的文件用的,而它呢,就是标准输入(一般是键盘,当然你也可以把它重定向到文件啦)的文件描述符。它的定义是一个int,是一个打开文件的句柄。这里又不得不说一下句柄,它与普通指针的区别是:句柄是一个系统标识,系统通过它可以重定向(或者说映射)到相应的内存地址上,可以说,它的封装性好,系统可以增强对引用对象的控制,不像指针一样可以随便乱指。
那么STDIN_FILENO对应的函数,也就是几个系统调用函数,比如说read、write、lseek、close等,书上说它们提供了不带缓冲的I/O,这个缓不缓冲还是放到后面看吧。
既然提到了file descriptor对应的系统调用函数,那么就来看看它和stdin有什么区别吧!
stdin的类型是FILE*,对应的函数主要是标准库<stdio.h>中实现的fread、fclose、fwrite等等,不是系统调用,只是高级的输入输出函数,File* stream;
STDIN_FILENO,类型是int,上面说过对应的是<unistd.h>中的系统调用,不带缓冲;
标准库里的fread的内部实现自然封装了系统调用read。说道这里,自然都明白了。
关于1-4的程序就说到这里吧,下面进入1-5。
1-5是和1-4大同小异,只是不是用系统调用实现copy,而是利用了标准I/O函数,这样就不用担心如何选取最佳的缓冲区大小,如1-4中BUFFSIZE的值了。
这里主要看一下这句代码:
<span style="font-family:SimHei;font-size:18px;">c = getc(stdin);</span>
在C标准库中有一大批类似的函数:fgetc、getc、getchar;fgets、gets。下面就来说说区别:
(推荐区分C语言中…… http://www.cnblogs.com/younes/archive/2010/01/05/1639482.html)
getc、getchar都是借助fgetc的宏定义实现的,比如说#define getchar() fgetc(stdin),但fgets和gets没有这个关系,慎用gets(具体见上述网址)。所以我们只来看一看fgetc和fgets的区别:
关于具体三个get char 和两个get string之间的区别,参考上述网页。<span style="font-family:SimHei;font-size:18px;">int fgetc ( FILE * stream ); //返回EOF则说明结束 char * fgets ( char * str, int num, FILE * stream ); //从stream中读取最多num个到str中,如果遇到换行///符或者字符数达到num-1则停止;一般来说,fgets都以换行符终止,后面紧接着一个NULL</span>
1-4和1-5是分别在系统调用和标准库函数下的输入输出,下面看接下来的内容。
下面的内容包括程序和进程、出错处理、用户标识、信号、时间等,又到十点多,马上该回宿舍了,先零散的记录一下,明天补充。
程序1-6中:
<span style="font-family:SimHei;font-size:18px;">(long)getpid();</span>
这是调用当前进程,getpid返回pid_t数据类型,虽然通常一个整型就可以表示进程ID,但强制转换成long是它可能用到的最大长度,可以提高移植性。
程序1-7是从标准输入读取命令并执行的程序,菜鸟认为需要掌握的内容比较多,包含了fork、exec(代码中用到的execlp属于它的七种变体之一,详见http://www.cnblogs.com/mickole/p/3187409.html)、waitpid。
fork函数创建一个新进程,是调用函数的一个副本,称为子进程。fork()对父进程返回子进程ID,对子进程返回0,所以它是被调用一次,返回两次。
UNIX中,fork和紧跟其后的exec就是其他一些操作系统中的spawn一个新进程,只是UNIX中将这两部分分为两个独立的函数。
关于1.7节出错处理,课后题有一个问题:the argument to perror is defined with the ISO C attribute const, whereas the integer argument to strerror isn’t defined with this attribute. Why?(主要是两个函数perror和strerror的区别,这里给出解答 http://www.beej.us/guide/bgnet/output/html/multipage/perrorman.html)
它们的定义为:
<span style="font-family:SimHei;font-size:18px;">#include <stdio.h>
#include <string.h> // for strerror()
void perror(const char *s);
char *strerror(int errnum);</span>
下面为示例:
<span style="font-family:SimHei;font-size:18px;">int s;
s = socket(PF_INET, SOCK_STREAM, 0);
if (s == -1) { // some error has occurred
// prints "socket error: " + the error message:
perror("socket error");
}
// similarly:
if (listen(s, 10) == -1) {
// this prints "an error: " + the error message from errno:
printf("an error: %s\n", strerror(errno));
}</span>
从这个示例很容易看出,perror前面可以加自己的描述(尚未搞清),strerror返回了针对errnum的详细信息,总之两者very similar。另外,错误有致命性的和非致命性的,致命性错误顶多输出一个提示或写入日志,不可恢复,而非致命性错误有多种措施,比如对于资源相关的非致命性错误的典型恢复操作是“延迟一段时间,然后重试”,例如,一个网络连接不起作用,那么应用程序可采用这种方法,短时间延迟后再重新尝试链接(一些应用使用指数补偿算法,在每次迭代中等待更长时间)。
1.8节 用户标识
root和superuser的用户ID为0,这个在上次把/etc权限弄乱的时候见识过。
getuid()和getgid()这两个函数可以查看当前user ID和group ID。
1.9节 signal (信号)
signals are a technology used to notify a process that some condition has occurred.比如除零操作的信号SIGFPE(floating-point exception)。
进程有以下三种处理信号的方式:
1. 忽略(不推荐)2. 按系统默认方式处理(除零的默认处理方式是终止进程) 3. 捕捉信号(提供自编的函数,我们就能知道什么时候产生了信号,并按期望的方式去处理)。
程序1-10中
<span style="font-family:SimHei;font-size:18px;">static void sig_int(int); /*our signal-catching function*/
......
if(signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal error");</span>
天呐,这是什么啊?好吧,来看看吧,其实很简单。SIGINT也就是中断信号。终端键盘上两种产生信号的方法:1)interrupt key(ctrl+c, delete)2)quit key(ctrl+\)。要了解上面的语句,那么显然要先看函数signal的定义啊:(查signal的时候,又是这个感想,千万慎用中文资料)
就看这个吧,http://www.tutorialspoint.com/c_standard_library/c_function_signal.htm
<span style="font-family:SimHei;font-size:18px;">void (*signal(int sig, void (*func)(int)))(int)</span>
这个是signal的定义,很清晰啊,int sig是那些SIGINT、SIGTERM等等macro的值,func当然是自己定义的用来捕捉前面信号并进行处理的函数啦。看下面示例:<span style="font-family:SimHei;font-size:18px;">#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void sighandler(int);
int main()
{
signal(SIGINT, sighandler);
while(1)
{
printf("Going to sleep for a second...\n");
sleep(1);
}
return(0);
}
void sighandler(int signum)
{
printf("Caught signal %d, coming out...\n", signum);
exit(1);
}</span>
while(1)里面的东西不断循环打印,直到接到一个SIGINT的信号(就假设我们按下ctrl+c啦),sighandler捕捉住并执行。就那么简单啊,apue书上还没说清楚。
好了,这就是信号这一节,下面关于1.10 Time values,第一章介绍的很少,记住UTC(Coordinated Universal Time协调世界时间),自1970.1.1 00:00:00以来的秒数,其它的用到再查。
接下来是1.11节 system calls and library functions系统调用和库函数。
这里边注意到了sbrk系统调用和malloc库函数,当然malloc的实现中用的是sbrk(2),如果不喜欢默认malloc的实现,可以写自己的malloc函数。
所以system call只提供了最小的接口,而library function提供复杂的功能。
还有一句话不理解:“Library functions aren't entry points into the kernel, although they may involve one or more of the kernel's system calls.”,比如printf是library function,内部实现调用了write system call,但有的函数比如说strcpy,atoi不使用任何系统调用。
下面是长长的分割线
————————————————————————————————————————————————————————————————————
好了,第一章看完了,大概了解了一些知识,当然远远不够。路漫漫其修远兮~~~