1、UNIX体系结构shell
全部操做系统都为他们所运行的程序提供服务,典型的服务包括:执行新程序、打开文件、读文件、分配存储区等。严格意义上来讲,操做系统能够定义为一种软件,它控制计算机硬件资源,提供程序运行环境。咱们也将这种软件成为内核,由于它相对较小,而且位于环境的核心。内核的接口被称为系统调用。公共函数库构建在之上,普通的应用程序能够调用系统调用,也能够调用公共函数库。shell是一个特殊的应用程序,位运行其余应用程序提供了一个接口。函数
2、文件和目录ui
1.文件系统spa
UNXI文件系统是目录和文件的一种层次结构,全部东西的起点称为根(root)的目录,这个目录的名称是一个字符“/”。目录是一个包含目录项的文件,由文件名和文件属性(文件类型、文件大小、文件全部者、文件权限等)组成。这里须要注意的是逻辑视图和实际存放在磁盘上的方式是不一样的。UNIX文件系统的大多数实现并不在目录项中存放属性,这是由于当一个文件具备多个硬连接时,很难保持多个属性副本之间的同步。操作系统
2.文件名和路径名线程
建立新目录时会自动建立两个文件名:“.”指向当前目录,“..”指向父目录,在最高层次,两者指向相同。由斜线分隔的一个或多个文件名组成的序列构成路径名,以斜线开头的路径名称为绝对路径名,不然称为相对路径名。code
#include<stdio.h> #include<stdlib.h> #include<dirent.h> int main(int argc, char *argv[]) { DIR *dp; struct dirent *dirp; if(2 != argc) { printf("usage: ls directory_name\n"); return -1; } if(NULL == (dp = opendir(argv[1]))) { printf("can't open %s\n", argv[1]); return -1; } while(NULL != (dirp = readdir(dp))) { printf("%s\n", dirp->d_name); } closedir(dp); exit(0); }
1-1:列出一个目录中的全部文件blog
3.工做目录和起始目录接口
每一个进程都有一个工做目录,全部相对路径名都从工做目录开始解释,进程能够用chdir函数更改其工做目录。登录时,工做目录设置为起始目录,这个目录是从口令文件中相应用户的登陆项中获取的。进程
3、输入和输出
1.文件描述符
文件描述符一般是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。当内核打开一个先用文件或建立一个新文件时,它都返回一个文件描述符,在读写文件时,可使用这个文件描述符。按惯例,每当运行一个新程序时,全部的shell都为其打开3个文件描述符,即标准输入、标准输出以及标准错误,若是不作特殊处理,这三个文件描述符都连接向终端。
2.不带缓冲的I/O
函数open、read、write、lseek以及close提供了不带缓冲的I/O。这些函数都使用文件描述符。
#include<unistd.h> #include<stdio.h> #include<stdlib.h> #define BUFFSIZE 4096 int main(void) { int n; char buf[BUFFSIZE]; while((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0) { if(write(STDOUT_FILENO, buf, n) != n) { printf("write error.\n"); return -1; } } if(n > 0) { printf("read error.\n"); return -1; } exit(0); }
1-2:将标准输入复制到标准输出
注释:1.头文件<unistd.h>包含了不少UNIX系统服务的函数原型,STDID_FILENO/STDOUT_FILENO,以及read/write都定义在其中。2.read函数返回其读取到的字节数,当发生错误时,read返回-1;3输入ctrl+D做为文件结束符,终止程序;
3.标准I/O
标准I/O函数为那些不带缓冲I/O函数提供了一个带缓冲的接口。使用标准I/O函数无需担忧如何选取最佳的缓冲区大小,不用像上例那样须要定义BUFFSIZE。而且还简化了对输入行的处理。
#include<unistd.h> #include<stdio.h> #include<stdlib.h> int main(void) { int c; while((c = getc(stdin)) != EOF) { if(putc(c, stdout) == EOF) { printf("output error.\n"); return -1; } } if(ferror(stdin)) { printf("input error.\n"); } exit(0); }
1-3:用标准I/O将标准输入复制到标准输出
4、程序和进程
1.程序、进程和进程ID
程序是一个存储在磁盘上某个目录中的可执行文件。内核使用exec函数将程序读入内存,并执行程序。程序的执行实例称为进程,每个进程都有一个惟一的数字标识符,被称为进程ID(非负整数)。
#include<unistd.h> #include<stdio.h> #include<stdlib.h> int main(void) { printf("Hello world from process ID %ld\n", (long)getpid()); exit(0); }
1-4:打印进程ID
2.进程控制
进程控制主要函数:fork、exec和waitpid。
#include<stdlib.h> #include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/wait.h> #define MAXLINE 1024 int main(void) { char buff[MAXLINE]; pid_t pid; int status; printf("%% "); while(NULL != (fgets(buff, MAXLINE, stdin))) { if('\n' == buff[strlen(buff) - 1]) { buff[strlen(buff) - 1] = 0; } if(0 > (pid = fork())) { printf("fork error\n"); return -1; } else if(0 == pid) { execlp(buff, buff, (char *) 0); printf("couldn't execute:%s\n", buff); return -1; } if(0 > (pid == waitpid(pid, &status, 0))) { printf("wait pid error\n"); } printf("%% "); } }
1-5:从标准输入读命令并执行
注释:1.fgets是一个标准I/O函数,一次读取一行。和read的区别在于本身管理缓冲区,不返回读取的长度,在读取的末尾加入"\n"。而execlp要求参数以null结束,因此须要进行替换;2.fork调用一次,返回两次,在子进程中返回0,父进程返回子进程PID;3.execlp以执行从标准输入读取的命令,这就用新的程序文件替换了子进程原先执行的程序文件;4.父进程和子进程同步是经过waipid实现的。
3.线程和线程ID
一个进程内的全部线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。由于他们能访问赞成存储区,因此各线程在访问共享数据时须要采起同步措施以免不一致性。和进程ID相同, 线程也有ID,可是线程ID只在它所属的进程内其做用。
5、出错处理
当UNIX系统函数出错时,一般会返回一个负值,并且整形变量errno一般被设置为具备特定信息的值。对于errno应当注意两条规则:1.若是没有出错,其值不会被例程清楚,所以,仅当函数的返回值指明出错时,才检验其值;2.任何函数都不会将errno值设为0.
#include<unistd.h> #include<stdlib.h> #include<string.h> #include<stdio.h> #include<errno.h> int main(int argc, char * argv[]) { fprintf(stderr, "EACCES: %s\n", strerror(EACCES)); errno = ENOENT; perror(argv[0]); exit(0); }
1-6:例示strerror和perror
注释:1.咱们将程序名(argv[0])做为参数传递给main,这是一个标准的UNIX惯例。
6、用户标识
口令文件登陆项中的用户ID是一个数值,它向系统标识不一样的用户。根用户的ID为0.
#include<unistd.h> #include<stdlib.h> #include<stdio.h> int main(void) { printf("uid = %d, gid = %d\n", getuid(), getgid()); exit(0); }
1-7:打印用户ID和组ID
7、信号
信号(signal)用于通知进程发生了某种状况。进程有如下三种处理方式:1.忽略信号;2.按系统默认方式处理;3.提供一个函数。该信号发生时调用该函数。当向一个进程发送信号时,咱们必须是那个进程的全部者或者是超级用户。
#include<stdlib.h> #include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/wait.h> #define MAXLINE 1024 static void sig_int(int); int main(void) { char buff[MAXLINE]; pid_t pid; int status; if(signal(SIGINT, sig_int) == SIG_ERR) { printf("signal error\n"); return -1; } printf("%% "); while(NULL != (fgets(buff, MAXLINE, stdin))) { if('\n' == buff[strlen(buff) - 1]) { buff[strlen(buff) - 1] = 0; } if(0 > (pid = fork())) { printf("fork error\n"); return -1; } else if(0 == pid) { execlp(buff, buff, (char *) 0); printf("couldn't execute:%s\n", buff); return -1; } if(0 > (pid == waitpid(pid, &status, 0))) { printf("wait pid error\n"); } printf("%% "); } } void sig_int(int signo) { printf("interrupt \n%% "); }
1-8:从标准输入读命令并执行
8、时间值
UNIX系统为一个进程维护了3个进程时间值:1.时钟时间:进程运行的时间总量,其值与系统中同时运行的进程数有关;2.用户CPU时间:执行用户指令所用的时间量;3.系统CPU时间:为该进程执行内核程序所经历的时间。2和3之和又称为CPU时间。
9、系统调用和库函数
从实现者的角度来看,系统调用和库函数之间有根本的区别,但从用户的角度来看,其区别不重要。用户能够替换库函数,可是不能替换系统调用。而且系统调用一般提供一种最小接口,而库函数一般提供比较复杂的功能。