Unix Architecture
Logging in
登录unix系统时,我们输入的账户密码需要在密码文件中检查。密码文件中包含了用户名,加密的密码,home目录,shell program(/bin/ksh)等等内容。
有些系统将密码存放到了其他的文件之中。
shells
shell是一种command-line interpreter用于读取用户输入并且执行指令。用户输入命令可以通过terminal也可以通过shell脚本 来输入。
Files and Directories
file system
filename
pathname
Working Directory
每个程序都有一个工作目录。比如doc/memo/joe,就是在工作目录下的doc中的memo目录中的joe目录或者文件。而/usr/lib就是绝对路径,根目录下的usr目录下的Lib目录。
Home Directory
Input and Output
File Descriptors
文件描述符是内核用来标志被特殊进程访问的文件,通常是小的非负整数。文件描述符可以用来完成read和write操作。
Standard input, Standard Output, and Standard Error
无论哪个程序运行的时候,所有的shell会打开三个描述符-对应于标准input/out/error>
当只是简单地运行一个命令的时候,如:
ls
then all three are connected to the terminal.
大部分shell提供重定向的方法,例如:
ls > file.list
就将standard ouput重定向到文件file.list中去。
Unbuffered I/O
无缓冲IO通过函数open,read,write,lseek,and close提供。这些函数都操作file descriptor.
Example
使用一个例子演示复制文件1到第二个文件中,使用了重定向。因为我是用的是ubuntu,与书中的代码进行了一些修改,本人亲自实验过,因此代码都是可用的。
#include <stdio.h>
#include <unistd.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)
{
fprintf(stderr, "write error");
}
}
if(n < 0)
{
fprintf(stderr, "read error");
}
return 0;
}
gcc编译成功后执行:
./a.out < infile > outfile
文件infile重定向为a.out的标准输入,outfile重定向为a.out的标准输出。执行该命令后,infile文件内容会复制到outfile中。
Standard I/O
标准IO在unbuffered I/O之上提供了具有缓冲能力的接口。使用标准IO不需要我们考虑选择合适的缓冲区大小,例如上例的BUFFSIZE。此外标准IO简化了对于输入一行一行的处理。例如:fgets
就直接读取完整的一行。最典型的标准IO函数就是printf
。
头文件为<stdio.h>
接下来对于上一个例子进行改写。
Example
#include <stdio.h>
int main(void)
{
int c;
while((c = getc(stdin)) != EOF)
{
if(putc(c, stdout) == EOF)
{
fprintf(stderr, "out error!");
}
}
if(ferror(stdin))
{
fprintf(stderr, "input error!");
}
return 0;
}
此外解释一下代码中的函数功能:
ferror(stdin)
用于检查流上的错误,如果标准输入流有错,则报错
Programs and Processes
Program
程序是在硬盘上的可执行文件。一个程序被读进内存中并且被内核执行,内核使用six exec functions中的一种来执行程序。具体的内容会在后续章节讲解。
Process and Process ID
进程是程序被执行的过程。一些操作系统使用术语task
来指代被执行的程序。
Unix系统使用非负整数的进程ID来区别进程。
Process Control
有三种主要函数进行进程控制:
fork
exec(has six variants)
waitpid
Threads and Thread IDs
通常进程仅仅有一个线程-同一时间一组机器指令执行。
在一个进程中所有的线程分享同样的地址空间、文件描述符、stacks、和进程相关的属性。因为共享的特性,所以需要注意线程同步问题。
Error Handling
在UNIX系统函数中产生错误的时候,会返回负数,此外errno
会被设置为某值—用于提供额外的信息。
此外一些函数发生错误会返回null
指针。
在头文件<errno.h>
定义了errno
如果你想在Linux中查询错误常量列表,你需要在errno(3) manual page
中查看。
对于errno有两点需要注意:
1. 如果function没有出错,则不会清除errno的值。因此只有在发生错误后查看errno的值才是有意义的。
2. errno永远不会被设置为0
C标准中有2个函数用来帮助打印错误信息:
#include <string.h>
char * strerror(int errnum);
#include <stdio.h>
void perror(const char *msg);
Error Recovery
定义在头文件<errno.h>
的错误可以被分为两类:致命的和非致命的。只有非致命的(nonfatal)可以被修复。资源相关的非致命错误中,EBUSY
表示共享的资源正在被使用。EINTR
表示interrupts a slow system call.
对于资源非致命错误典型的处理办法就是等待一会儿再试一次。
1.8 User Identification
User ID
用于标示用户,ID 0就是root
的用户ID。
Group ID
用户成组,便于共享资源等。
1.9 Signals
信号是一种通知进程一些环境已经发生了改变的技术。例如,进程除以0,名为SIGFPE
的信号就会发送给进程。
每个进程有三个选择来处理信号:
1. 忽略信号
2. 执行默认的操作
3. 提供自定义的函数来执行。
很多环境都有信号产生,例如终端使用Ctrl+C来打断进程的执行。
另外一个产生信号的方法是调用kill
function。我们可以调用这个函数来发送信号给另一个进程。
但是:我们必须是另一个进程的拥有者才可以这样做
1.10 Time Values
UNXI系统拥有两种时间值:
1. 日历时间
2. 进程时间
对于每个进程拥有三种时间值:
1. clock time(wall clock time)进程运行的总时间,该值也取决于系统运行的其他进程。
2. User CPU time,用户指令的CPU时间。
3. System CPU time,内核中运行该进程的时间。
为了测量这些时间,只需要简单的执行time(1)
命令。例如:
cd /user/include
time -p grep _POSIX_SOURCE */*.h > /dev/null