1.2 UNIX体系结构
从严格意义上,可将操作系统定义为一种软件(内核),它控制计算机硬件资源,提供程序运行环境。内核的接口被称为系统调用。公用函数库构建在系统调用接口之上,应用软件即可使用公用函数库,也可使用系统调用。shell是一种特殊的应用程序,它为运行其他应用程序提供了一个接口
从广义上,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并给予计算机以独有的特性(软件包括系统实用程序,应用软件,shell以及公用函数库等)
1.3 shell
shell是一个命令行解释器,它读取用户输入,然后执行命令。用户通常用终端(交互式shell),有时则通过文件(shell script)向shell进行输入
UNIX系统常见shell:
名称 路径
Boune shell: /bin/sh
Bourne-again shell: /bin/bash
C shell: /bin/csh
Korn shell: /bin/ksh
TENEX C shell: /bin/tcsh
C shell其控制流类似于C语言,它支持Bourne shell没有的某些特色功能,例如作业控制、历史记忆机制以及命令行编辑等
Korn shell它与Bourne shell向上兼容,并具有使C shell广泛得到应用的一些特色功能,包括作业控制以及命令行编辑
Bourne-again shell是GNUshell,所有linux都提供这种shell,它被设计成遵循POSIX的,同时也保留了与Bourne shell的兼容性。它支持C shell和Korn shell两者的特色功能
TENEX C shell 是C shell的加强版本。它从TENEX操作系统借鉴了很多特色,例如命令完备。TENEX C shell在C shell的基础上增加了很多特征,常被用来替换C shell
注:linux默认shell是Bourne-again shell.事实上,/bin/sh将链接到/bin/bash.FreeBSD 和Mac OS X的默认用户shell是TENEX C shell,但是因为使用C shell编程语言极其困难,所以它们使用Bourne shell编写用于管理方面的shell脚本
1.4文件和目录
1.文件系统
UNIX文件系统是目录和文件组成的一种层次结构,目录的起点称为根(root),其名字是一个字符/。文件属性是指文件类型(是普通文件还是目录)、文件大小、文件所有者、文件权限以及文件最后的修改时间等。
2.文件名
目录中的各个名字称为文件名。不能出现在文件名中的字符只有斜线(/)和空操作符(null)两个。
创建新目录时会自动创建两个文件名:.(当前目录),..(父目录)
3.路径名
以斜线开头的路径名称称为绝对路径名,否则称为相对路径名
实例:1-1 列出一个目录中的所有文件
1 #include"apue.h" 2 #include<dirent.h> 3 int main(int argc,char *argv[]) //argc表示参数个数;argv表示参数内容 4 { 5 DIR *dp; 6 struct dirent *dirp; 7 if(argc!=2) 8 err_quit("usage:ls directory_name"); 9 if((dp=opendir(argv[1]))==NULL) //opendir函数返回指向DIR结构的指针 10 err_sys("can‘t open%s",argv[1]); 11 while((dirp=readdir(dp))!=NULL) //readdir返回一个指向dirent结构的指针 12 printf("%s\n",dirp->d_name); 13 closedir(dp); 14 exit(0); 15 }
这段代码类似于ls命令,不过这段代码只打印一个目录中各个文件的名字,不显示其他信息
编译方法:$ cc xxx.c 编译
$ ./a.out 运行
(可能很多初学者在编译这段代码时会出现找不到apue.h,这里我发个配置apue.h头文件的链接 http://www.linuxidc.com/Linux/2013-01/77686.htm)
1.5输入与输出
1.文件描述符
文件描述符通常是一个小的非负整数,内核用它标识一个特定进程访问的文件。当内核打开一个已有文件或创建一个新文件时,它返回一个文件描述符。在读写文件时,就可以使用它。
3.不用缓冲的I/O
函数open、read、write、lseek以及close提供了不用缓冲的I/O。这些函数都使用文件描述符
程序清单1_2 将标准输入复制到标准输出
1 #include"apue.h" 2 #define BUFFSIZE 4096 3 int main(void) 4 { 5 int n; 6 char buf[BUFFSIZE]; 7 while((n=read(STDIN_FILENO,buf,BUFFSIZE))>0) 8 if(write(STDOUT_FILENO,buf,n)!=n) 9 err_sys("write error"); 10 if(n<0) 11 err_sys("read error"); 12 exit(0); 13 }
4.标准I/O
标准I/O函数提供一种对不用缓冲I/O函数的带缓冲的接口。使用标准I/O函数可以无需担心如何选取最佳的缓冲区大小,例如1_2中BUFFSIZE常量的大小。使用标准I/O函数的另一个优点是简化了对输入行的处理。例如,fgets函数读一完整的行,而read函数读指定字节数。(printf函数便是标准I/O函数)
实例:1_3 用标准I/O将标准输入复制到标准输出
1 #include"apue.h" 2 int main() 3 { 4 int c; 5 while((c=getc(stdin))!=EOF) 6 if(putc(c,stdout)==EOF) 7 err_sys("output error"); 8 if(ferror(stdin)) 9 err_sys("input error"); 10 exit(0); 11 }
附:无缓冲I/O操作和标准I/O操作的区别 http://blog.csdn.net/cowbane/article/details/6630298
1.6程序和进程
1.程序是存放在磁盘上,处于某个目录中的一个可执行文件。使用6个exec函数中的一个由内核将程序读入存储器,并使其执行
2.进程和进程ID
程序的执行实例被称为进程。UNIX系统确保每个进程都有一个惟一的数字标识符,称为进程ID,进程ID总是一非负整数
实例:1_4 打印进程ID
1 #include"apue.h" 2 int main() 3 { 4 printf("hello world from process ID %d\n",getpid()); 5 exit(0); 6 }
此程序运行时,它调用函数getpid得到其进程ID
3.进程控制
有三个用于进程控制的主要函数:fork,exec和waitpid
实例: 1_5 从标准输入读命令并执行(类shell程序)
1 #include"apue.h" 2 #include<sys/wait.h> 3 4 5 int main(void) 6 { 7 char buf[MAXLINE]; 8 pid_t pid; 9 int status; 13 printf("%% "); 14 while(fgets(buf,MAXLINE,stdin)!=NULL){ 15 if(buf[strlen(buf)-1]==‘\n‘) 16 buf[strlen(buf)-1]=0; //用null替换换行符 17 if((pid=fork())<0){ //fork向父进程返回新子进程的进程ID(非负),对子进程则返回0;所以说它被调用一次,返回两次 18 err_sys("fork error"); 19 } 20 else if(pid==0){ 21 execlp(buf,buf,(char *)0); //execlp执行从标准输入读入的命令。fork和跟随其后的exec两者的组合是某些操作系统所称的产生(spawn)一个新进程 22 err_ret("couldn‘t execute:%s",buf); 23 exit(127); 24 } 25 if((pid=waitpid(pid,&status,0))<0) //waitpid函数返回子进程的终止状态 26 err_sys("waitpid error"); 27 printf("%% "); 28 } 29 exit(0); 30 }
该程序的主要限制是不能向所执行的命令传递参数,例如不能指定要列表的目录名,只能对工作目录执行ls命令
4.线程和线程ID
在一个进程内的所有线程共享同一地址空间、文件描述符、栈以及进程相关的属性。线程ID只在它所属进程内起作用。一个进程中的线程ID在另一个进程中并无意义。
1.7出错处理
当unix函数出错时,常常返回一个负值,而且整型变量errno通常被设置为含有附加信息的一个值。例如,open函数如成功执行则返回一个非负文件描述符,如出错则返回-1。
实例:1_6 示例strerror和perror两个出错函数的使用方法
1 #include"apue.h" 2 #include<errno.h> 3 int main(int argc,char *argv[]) 4 { 5 fprintf(stderr,"EACCES: %s\n",strerror(EACCES)); 6 errno=ENOENT; 7 perror(argv[0]); 8 exit(0); 9 }
1.8用户标识
用户ID 和组ID
示例:1_7 打印用户ID和组ID
1 #include"apue.h" 2 int main() 3 { 4 printf("uid=%d,gid= %d,\n",getuid(),getgid()); 5 exit(0); 6 }
1.9信号
信号是通知进程已发生某种情况的一种技术。
进程处理信号有三种选择:a.忽略该信号
b.按系统默认方式处理。对于除以0的情况,系统默认是终止该进程
c.提供一个函数,信号发生时则调用该函数,这被称为捕捉该信号。使用这种方式,我们只要提供自编的函数就将能知道什么时候产生了信号,并按所希望 的方式处理它
实例:1_8 从标准输入读命令并执行(比1_5多了信号处理)
1 #include"apue.h" 2 #include<sys/wait.h> 3 static void sig_int(int); 4 5 int main(void) 6 { 7 char buf[MAXLINE]; 8 pid_t pid; 9 int status; 10 if(signal(SIGINT,sig_int)==SIG_ERR) 11 err_sys("signal error"); 12 13 printf("%% "); 14 while(fgets(buf,MAXLINE,stdin)!=NULL){ 15 if(buf[strlen(buf)-1]==‘\n‘) 16 buf[strlen(buf)-1]=0; 17 if((pid=fork())<0){ 18 err_sys("fork error"); 19 } 20 else if(pid==0){ 21 execlp(buf,buf,(char *)0); 22 err_ret("couldn‘t execute:%s",buf); 23 exit(127); 24 } 25 if((pid=waitpid(pid,&status,0))<0) 26 err_sys("waitpid error"); 27 printf("%% "); 28 } 29 exit(0); 30 } 31 void sig_int(int signo) 32 { 33 printf("interrupt\n%%"); 34 }
1.10时间值
UNIX系统使用三个进程时间值:时钟时间,用户cpu时间,系统cpu时间
时钟时间又称为墙上时钟时间(wall clock time)。它是进程运行的时间总量,其值与系统中跟同时运行的进程数有关。
用户CPU时间是执行用户指令所用的时间。
系统cpu时间是为该进程执行内核程序所经历的时间
例如:每当一个进程执行一个系统服务时,如read或write,则在内核内执行该服务所花费的时间就计入该进程的系统cpu时间。用户cpu时间和系统cpu时间之和常被称为cpu时间
取得任意进程的时钟时间,用户时间,和系统时间:$cd /usr/include;time -p grep _POSIX_SOURCE */*.h > /dev/null
APUE学习笔记:第一章 UNUX基础知识