在研读了鸟哥的linux私房菜之后,对linux有了大致的概念,下面就开始研读linux学习中被称为圣经的:《Advanced Programming in unix environment》,也就是众所周知的unix环境高级编程。
unix体系结构:
操作系统实际上是一个软件,也就是内核,而内核的接口被称为系统调用(system call),公用函数库构建在系统调用接口之上,应用程序既可调用函数库,也可使用系统调用。
登陆shell:
shell 是一个命令解释器,读取用户输入,然后执行命令,用户有时通过终端shell,有时通过文件(shell脚本)向shell进行输入,常见的shell如下所示
文件和目录:
unix文件系统是目录和文件组成的一种层次结构,当创建新目录时会自动创建两个文件名, .和.. ,其中点指向当前目录,点点指向父目录,在最高层次的根目录中,.和..没有区别。
ls的c语言编程实现
准备工作:linux下c的头文件都放在/usr/include目录下,在unix这本书中,有很多源代码,但里面都包含了一个头文件apue.h,是作者自己写的,linux并不包含这个,因此需要在官网下载这个头文件,并放在/usr/include下,官网:http://www.apuebook.com/。并且要在apue.h的最后一行,也就是#endif之前一行,加上#include "error.c"。然后将下载的源代码中..apue.2e/lib目录下找到error.c这个文件,放到/usr/include目录下即可。
源代码:
#include"apue.h"#includeint main( int argc , char *argv[]) //将命令行的第一个参数argv[1]作为目录名。 { DIR *dp; struct dirent *dirp; if(argc !=2) //当命令行参数不等于2,也就是没有写要列出的目录时,会报错 err_quit("usage: ls directory_name"); if((dp =opendir(argv[1]))==NULL) //调用opendir函数,返回指向dir结构的指针,当返回值dp是null,表明不存在这个目录 err_sys("can't open %s",argv[1]); while ((dirp = readdir(dp)) !=NULL) //将这个dp指针传递给readdir,这里用到while循环结构,一直到readdir函数无目录可读返回null时,结束程序。 printf("%s\n",dirp->d_name); closedir(dp); exit(0); //程序结束,以参数0调用exit函数,表示正常结束,参数值1-255表示出错。 }
编译好后,默认名是a.out
也就是./a.out 相当于ls命令
比如
程序和进程:
unix确保每一个进程都有一个唯一的数字标识符,称为进程ID,进程ID总是一非负数。
源代码:打印进程ID
#include "apue.h"intmain(void){ printf("heollo world from process ID %d\n",getgid()); //调用getgid函数来调用进程ID exit(0);}
getpid返回一个pid_t数据类型,我们不知道其大小,仅知道的是标准会保证它能保存在一个长整型中。
进程控制:
有三个用于进程控制的主要函数:fork、exec和waitpid
源代码:从标准输入命令并执行
#include "apue.h"#includeint main(){ char buf[MAXLINE]; pid_t pid; int status; printf("%% "); while (fgets(buf,MAXLINE,stdin)!=NULL) //标准fgets从标准输入一次读一行,当输入文件结束字符(ctrl+d)作为行的第一个字符 //,fgets返回一个null,循环终止,退出进程 {, if(buf[strlen(buf) -1]=='\n') //用null代替换行符,因为execlp函数要求参数以null结束 buf[strlen(buf) -1]=0; if((pid=fork())<0) //调用fork创建一个新进程,这个新进程称为子进程,fork向父进程返回子进程的ID(非负),对子进程返回0 { err_sys("fork error"); } else if(pid == 0) //子进程 { execlp(buf,buf,(char *)0); //在子进程中,调用execlp以执行从标准输入读入的命令, err_ret("couldn't execute: %s",buf); exit(127); } if((pid = waitpid(pid, &status,0))<0) // 父进程等待子进程结束,waitpid返回子进程的终止状态, err_sys("waitpid error"); printf("%% "); } exit(0);}
这个程序利用标准IO函数fgets从标准输入一次读一行,execlp函数执行新程序文件。
int execlp(const char * file,const char * arg,....);
execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针NULL)作结束。如果用常数0来表示一个空指针,则必须将它强制转换为一个字符指针,否则将它解释为整形参数,如果一个整形数的长度与char * 的长度不同,那么exec函数的实际参数就将出错。如果函数调用成功,进程自己的执行代码就会变成加载程序的代码,execlp()后边的代码也就不会执行了。
该程序的主要限制就是不能向所执行的命令传递参数,例如不能指定要列出目录项的目录名,只能对工作目录执行ls命令。如果要传递参数,先要分析输入行,然后用某种约定将参数分开,再传递给execlp函数。
信号:
通知进程已发生某种情况的一种技术,进程如何处理信号有三种选择,1.忽略该信号;2 按系统默认的方式处理 ;3 提供一个函数,信号发生时调用这个函数。
终端键盘上有两种产生信号的方法,分别称为中断键(delete或者crtl+c)和退出健。被用于中断当前的进程,另一种产生信号的方法是调用名为kill的函数。
出错处理:
当unix系统函数出错时,通常返回一个负值,而且整型变量errno通常被设置为具有特定信息的值,在open函数出错时,有大约15种不同的errno值。
头文件errno.h定义了errno以及可以赋予它的各种常量,这些常量都是以字符E开头。
时间值:
当度量一个进程的执行时间时,unix系统使用三个进程时间值:
时钟时间(墙上时间):也就是进程从开始到结束所用的实际时间
用户cpu时间:执行用户指令所用的时间
系统cpu时间:该进程执行内核程序所用时间
后两者时间和常称为cpu时间,执行time()指令,可以得到这三个值。
那么如果多核cpu并行处理指令,会导致时钟时间(wall time)会小于后俩者之和。
系统调用和库函数:
从实现者角度系统调用和库函数之间有重大区别,但是对于用户,区别并不重要。但是应该知道的是,必要时我们可以替代库函数,却无法替代系统调用
以存储器分配函数malloc,它实际上是调用了系统调用函数sbrk。也就是说,很多库函数是调用系统调用,而应用程序既可以调用库函数,也可以调用系统调用。
系统调用和库函数另一个差别是:系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能。