unix及类unix系统架构
操作系统严格意义上来说是由控制计算机硬件资源,提供程序运行环境的内核及其他软件构成的软件,其他软件包括系统实用程序(system utility)、应用程序、shell及公用函数库。\
操作系统的体系架构是硬件-内核-(shell,公用函数库,应用程序)。内核的接口就是系统调用,应用程序可以直接进行系统调用,也可以通过公用函数库进行系统调用。shell是一个特殊的应用程序,在用户登录类unix系统的时候,系统会提供一个视窗,视窗中会运行一个shell程序,通常是交互式的shell,用于读取用户的输入并给出输出。
文件和目录
目录,是一个包含目录项的文件,每个目录项包含一个文件名和文件属性。 \
#include
#include
#include
#include
int main(int argc, char *argv[]) {
DIR *dp; //
struct dirent *dirp;
if (argc != 2)
printf("usage: ls directory_name");
if ((dp = opendir(argv[1])) == NULL)
printf("can't open %s\n", argv[1]);
while ((dirp = readdir(dp)) != NULL) {
printf("%s\n", dirp->d_name);
}
return 0;
}
这是一个目列出所有目录项的名称的脚本,简化版的ls命令。
输入输出
文件描述符
fd(file descriptor)通常是一个小的非负整数,内核以这个标识进程正在访问的文件,内核打开或者创建一个文件的时候都会返回一个文件描述符,读写文件时都是用这个文件描述符进行操作。
标准输入、标准输出和标准错误(stdin, stdout, stderr)
shell都会打开这三个文件描述符,这三个文件描述符有特定的名称,STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO。
不带缓冲的I/O
open、read、write、lseek和close。
标准输入读入,写到标准输出示例
#include
#define BUFFSIZE 10
int main() {
int n;
char buf[BUFFSIZE];
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0) {
if(write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if (n < 0)
err_sys("read error");
}
return 0;
}
标准I/O及示例
标准I/O是对输入行进行处理,例如fgets函数读取一个完整行,printf是标准IO函数。
#include
#include
#define BUFFSIZE 10
int main() {
int c;
while ((c = getc(stdin)) != EOF) {
if (putc(c, stdout) == EOF)
err_sys("output error");
}
if (ferror(stdin))
err_sys("input error");
return 0;
}
程序和进程
程序
程序是存储上的某个可执行文件,内核使用exec函数将程序读入内存并执行。
进程和进程ID
进程ID(PID)是进程的唯一标识符,非负整数。
#include
int main() {
printf("process id is %ld\n", (long)getpid());
return 0;
}
进程控制
进程控制的主要函数有fork、exec和waitpid。
#include
#include
int main() {
char buf[MAXLINE];
pid_t pid;
int status;
printf("%% ");
while (fgets(buf, MAXLINE, stdin) != NULL) {
if (buf[strlen(buf) - 1] == '\n') {
buf[strlen(buf) - 1] = 0;
}
if ((pid = fork()) < 0) {
err_sys("fork error");
}
else if (pid == 0) {
execlp(buf, buf, (char *)0);
err_ret("could not execute: %s", buf);
return 127;
}
if ((pid = waitpid(pid, &status, 0)) < 0) {
err_sys("waitpid error");
}
printf("%% ");
}
return 0;
}
线程和线程ID
线程的特点是一个进程内的所有线程共享同一个地址空间、文件描述符、栈以及进程相关的属性,因为线程访问同一存储区,需要采取同步措施保持数据一致性。线程ID只能在该进程内有效,控制函数和进程控制函数类似。
出错处理
出错恢复,对于与资源相关的非致命性出错:EAGAIN、ENFILE、ENOBUFS、ENOLCK、ENOSPC、EWOULDLOCK以及ENOMEM有时也是非致命性的, 可以采取延迟一段时间再次调用,提高程序的鲁棒性。
用户标识
信号
信号(signal)用于通知进程发生了某种情况,比如除数是0时会将SIGFPE(浮点异常)的信号发给进程。进程面对信号有忽略信号,系统默认(一般是中止)和提供函数调用,用来对相应的信号进行处理。 \
一个信号实例是,Ctrl+C中断进程的时候,进程接收到信号,默认处理是中止进程。
#include
#include
void sig_int (int signo) {
printf("interrupt\n%% ");
}
int main() {
char buf[MAXLINE];
pid_t pid;
int status;
if (signal(SIGINT, sig_int) == SIG_ERR) {
err_sys("signal error");
}
printf("%% ");
while (fgets(buf, MAXLINE, stdin) != NULL) {
if (buf[strlen(buf) - 1] == '\n') {
buf[strlen(buf) - 1] = 0;
}
if ((pid = fork()) < 0) {
err_sys("fork error");
}
else if (pid == 0) {
execlp(buf, buf, (char *)0);
err_ret("could not execute: %s", buf);
return 127;
}
if ((pid = waitpid(pid, &status, 0)) < 0) {
err_sys("waitpid error");
}
printf("%% ");
}
return 0;
}
signal函数可以在接收到SIG_ERR信号的时候执行sig_int函数。
时间值
- 时钟时间
- 用户CPU时间
- 系统CPU时间
系统调用和库函数
从用户角度来来看,系统调用和库函数没有区别,都是为应用程序提供服务,但是从实现者的角度来看,两者是有区别的,库函数并不是内核的入口,系统调用才是,库函数可能不会进行系统调用,也可能进行一个或者多个系统调用。printf这个库函数,实际会调用write这个系统调用,函数strcpy和atoi并不适用任何的系统调用。 \
malloc函数进行存储分配,实际是通过sbrk系统调用完成的,sbrk分配空间给进程,malloc函数在用户层次管理这一空间。类似的还有时间函数,系统调用会给出自1970年1月1日至今的秒数,由时间函数进行处理给出常用的时间格式。