2009年3月3日星期二

Linux0.01分析笔记(4)

[gaowei@localhost ~]$ sh -e
sh-3.2$ exit
exit
[gaowei@localhost ~]$ man sh
-e参数指定“\"不用做转义字符。
当shell启动时,会读取默认的配置文件/profile。
[gaowei@localhost ~]$ \ls这样可以但是[gaowei@localhost ~]$ ls\ls 就bash: lsls: command not found
[gaowei@localhost ~]$ sh \ ls
sh: ls: 没有那个文件或目录
[gaowei@localhost ~]$ sh \
>
[gaowei@localhost ~]$ sh
sh-3.2$ \
>
[gaowei@localhost ~]$ "ls" kernel/
book C kang kernel linux-0.11 man-pages-3.10
当执行shell时,用户键入一个命令,shell首先除去所有的"\"字符之前的字符(如果没有使用-e参数)和所有的引号中的字符。然后,shell按照如下的规则处理"!"引起的shell命令历史代替:
!! 代表前一个执行的命令
! 代表前个执行的命令
! 代表字符串开始的命令
然后,shell会将输入的命令字符串切换成为令牌(token)字符串。在引导中的所有字符串成为一个令牌。空格是隔开各个令牌的分割符。
然后,shell使用如下的规则处理命令行的通配符:
包含通配符(当前设定为*)的令牌被假定成文件名,通配符被扩展成为匹配的文件名(*匹配任何字符)。
shell执行I/O重定向(I/O readirections):
'> word' 重定向标准输出(redirect stdout)
'>> word' 重定向标准输出,添加在指定的文件后
'>& amp;word' 重定向标准输出和标准错误输出(redirects stdout and stderr)
'< word' 重定向标准输入(redirects stdin)

shell处理的文件描述符一般有三个:文件句柄0(stdin,标准输入),文件句柄1(stdout,标准输出),文件句柄2(stderr,标准错误输出)。

dup()系统调用的功能是返回一个新的文件描述符,它指向传入参数文件描述符指向的文件流。dup()使用进程文件分配表中的第一个没有使用的文件句柄复制为新的文件句柄。dup()经常使用的一个功能就是完成标准I/O的重定向。使用dup()进行I/O重定向的代码片段如下:
int fd,pid;
fd = open(path,mode);
if((pid=fork())==0) {
close(0);/*Close stdin.*/
/* Make stdin come from fd.Dup() will reuse the lowest.
*number unused file descriptor,0 in this case.*/

dup(fd);
close(fd); /*No longer needed.*/
execve(...); /*Run command,with input from fd..*/
} else {
/*Parent.Wait for child processes to exit.*/
while(wait(NULL)>0);
}
pipe()函数可以创建一个FIFO流(First In First Out stream,先进先出流)来完成进程间的通信。pipe()系统调用生成俩个文件句柄,一个可以向FIFO流中写入,一个可以从FIFO流中读取。可以通过在父进程中使用pipe()函数来创建管道,然后再生成fork()子进程,每一个生成的子进程中都有父进程调用pipe()函数生成的文件句柄的复制。在子进程中,>可以把父进程中通过pipe()生成的句柄使用dup()函数来重定向子进程的标准输入(stdin),标准输出(stdout)和标准错误输出,这样父进程就可以控制子进程的标准I/O了。
int i,pid,mypipe[2];/*Two fds,for pipe ends.*/
pipe(mypipe); /*Create pipe.*/
/*Create reader.*/
if((pid=fork())==0) {
close(0); /*Close stdin.*/
dup(pipe[0]); /*Stdin comes from pipe.*/
close(pipe[0]); /*No longer needed.*/
close(pipe[1]); /*Not needed.*/
execve(...); /*Run command,with input from pipe.*/
}
/*Create write.*/
if((pid=fork())==0) {
close(1); /*Close stout.*/
dup(pipe[1]); /*stdout goes to pipe.*/
close(pipe[1]); /*No longer needed.*/
close(pipe[0]); /*Not needed.*/
execve(...); /*Run command,with output to pipe.*/
}
/*Parent.*/
close(pipe[0]);
close(pipe[1]);
while(wait(NULL)>0); /*Wait for child processes to exit.*/
一个例子:
[gaowei@localhost 10SHELL编程技术和实例]$ gcc -o dup dup.c
[gaowei@localhost 10SHELL编程技术和实例]$ ls
10 a.out dup dup.c sh1.c sh2 sh2.c
[gaowei@localhost 10SHELL编程技术和实例]$ ./dup
This is to stdout
This is to stderr
[gaowei@localhost 10SHELL编程技术和实例]$ ls
10 a.out dup dup.c err out sh1.c sh2 sh2.c
[gaowei@localhost 10SHELL编程技术和实例]$ cat ./out
This is to stdout
[gaowei@localhost 10SHELL编程技术和实例]$ cat ./err
This is to stderr
[gaowei@localhost 10SHELL编程技术和实例]$ ls -a
. .. 10 .10.swp a.out dup dup.c err out sh1.c sh2 sh2.c
[gaowei@localhost 10SHELL编程技术和实例]$ ls
10 a.out dup dup.c err out sh1.c sh2 sh2.c
[gaowei@localhost 10SHELL编程技术和实例]$
一个成熟的shell应该支持管道操作总体结构如下:
(cmd1 | cmd2) /* cmd1的标准输出为cmd2的标准输入 */
pipe[fdarr]; /*定义管道 */
if((pid1 = fork()) == 0) {
close(1); /*关闭cmd1的stdout */
dup(fdarr[1]); /* 设置fdarr[1]为cmd1的stdout */
close(fdarr[0]);

aptr[0]="cmd1"; aptr[1]=NULL;
execve("cmd1",aptr,eptr);
}
if((pid2=fork()) ==0) {
close(0); /*关闭cmd2的stdin */
dup(fdarr[0]); /*设置fdarr[0]为cmd2的stdin */
close(fdarr[0]);
close(fdarr[1]);

aptr[0]="cmd2";aptr[1]=NULL;
execve("cmd2",aptr,eptr);
}
close(fdarr[0]);
close(fdarr[1]);
wait() for both pid1 and pid2;
shell程序的主程序组成如下:
#include "def.h"
main(void)
{
int i;
initcold();
for(;;) {
initwarm();
if(getline())
if(i=parse())
execute(i);
}
}

第十一章
分析Linux0.01中实现系统调用的俩个文件system_call.s和sys.c来展示Linux0.01的系统调用的实现方式。标准的C语言库函数,在不同的操作系统上有不同的内部实现。
应用程序可以通过一个固定的过程,从而调用内核提供的功能,在Intel体系结构的计算机中,这是通过执行中断0x80h实现的。
应用程序通常是一个进程,进程在调用内核时,跳转到内核代码中的位置一般标记为system_call(在Linux0.01中,system_call是汇编程序system_call.s中的一段代码的入口点的标记)。在system_call位置的代码将检查系统调用号,依据系统调用号告诉系统内核进程请求的系统服务是什么。然后,它再查找系统调用表sys_call_table[],找到希望调用的内核函数的地址,并调用此函数,最后将控制权返回应用程序。编写一个自己的函数,然后改变sys_call_table[]中的指针并指向该函数。
Linux用于实现系统调用异常的实际指令是:int $0x80
定义系统调用的预定义宏为: _syscallN(parameters)
在include/unistd.h中可以找到_syscallN(parameters)还有__NR_name的形式定义了66个常数,这些常数就是系统调用函数name的函数指针在系统的调用表中的偏移量。
系统调用表是一张表格,按照顺序定义了系统中所有的系统调用的入口函数地址。在Linux0.01中,系统调用表定义在include/linux/sys.h中,如下所示:
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp,sys_setsid};
在数组sys_call_table[]中的每一个元素都是一个函数指针(在C语言中,函数名代表指向函数入口的指针),按系统调用号(即前面提到的_NR_name)排列了所有系统调用函数的指针,以供系统调用入口函数查找。从这张表可以看出,Linux给它所支持的系统调用函数取名叫sys_name。
系统调用入口函数定义在/linux/kernel/system_call.s文件中。是使用汇编语言书写的,包含了系统调用的主要处理程序,同时也包含了时钟中断处理程序,硬盘中断处理程序也包含在本文件中。
从system_call入口的汇编程序的主要功能是:
检查是否为合法的系统调用。
保存寄存器的当前值。
根据系统调用表_sys_call_table和EAX寄存器持有的系统调用号找出并转入系统调用响应函数。从该响应函数返回后,让EAX寄存器保存函数返回值,跳转至ret_from_sys_call,>当ret_from_sys_call结束后,将执行进程调度。
在执行位于用户程序中系统调用命令后面余下的指令之前,若INT 0x80h的返回值非负,则直接按类型type返回;否则,将INT 0x80h的返回值取绝对值,保留在errno变量中,返回-

第十二章
关于这一章我记录的比较少
尽管在Linux0.01中没有写网络的部分,但是现在是个网络的时代,这本书中也讲了关于网络的部分,而且我又是学习网络的,所以就在写一写了。
Linux是一个网络操作系统。
TCP/IP协议是一套数据通信协议,其名字是由这些协议中的俩个主要的协议组成的,即传输控制协议(Transmission Control Protocol,TCP)和网间协议(Internet Protocol,IP)。

time