前置知识: 测试的OS环境:
BSD:Berkeley software Distribution 加州大学伯克利分校开发的类unix系统
SVRx:表示AT&T的系统V的第x版
XPG3: X/Open可移植性指南
ANSI C:C语言的ANSI标准
POSIX.1: 是IEEE与ISO的类UNIX系统标准接口
1.1体系结构
操作系统:一种软件,控制硬件资源,提供程序运行环境,通常这种软件称为==内核(Kernel)== 组成:
内核:相对较小,且位于环境的核心,所以称为内核,
系统调用(System call):内核的接口,公用函数库构建在系统调用之上
公用函数库
shell
系统实用程序(System utility)
应用程序
1.2登录
用户名➡ 口令(口令文件为/etc/passwd)
口令文件登录项组成
sar:x:205:105:Stephen Rago:/home/sar:/bin/ksh
#sar:用户名
#x:加密口令
#205:用户id
#105:组id
#Stephen Rago:注释字段
#home/sar:起始目录
#bin/ksh:shell程序
1.3 Shell
shell:实际上是一个命令行解释器,命令可来自终端和脚本
常见shell如下 PS:bash 是 GNU shell, 所有Linux系统都提供这种shell,遵循POSIX标准,同时保留sh兼容性
1.4 文件与目录
1.文件系统
一切皆文件!目录也是一个文件,但是包含目录项
PS:目录项的逻辑视图与实际存放在磁盘的方式是不同的。UNIX文件系统的大多数实现并不在目录项中存放属性,这是因为当一个文件具有多个硬链接时,很难保持多个属性副本之间的同步
stat和fstat函数返回包含所有文件属性的一个信息结构
目录:
DIR 结构体,表示一个目录
dirent 结构体,表示目录中每个实例
以ls程序为例:
#include "apue.h"
#include <dirent.h>
int main(int argc, char *argv[])
{
DIR *dp;
struct dirent *dirp;
if((dp = opendir(argv[1])) == NULL)
err_sys("can't open %s", argv[1]);
while ((dirp = readdir(dp)) != NULL)
print("%s\n", dirp->d_name);
closedir(dp);
exit(0);
}
PS:起始工作目录可以在口令文件中修改
1.5 输入与输出
1.文件描述符(file descriptor) 通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件
2.标准输入、标准输出和标准错误 按惯例,每当运行一个新程序时,所有的shell都为其打开3个文件描述符,即stdin, stdout,stderr 例: ls > file.list ,为把stdin从默认的绑定到终端修改为重新定向到文件file.list
在<unistd.h>中定义了的常量STDIN_FILENO,STDOUT_FILENO指定了标准输入输出的文件描述符,在POSIX标准中,他们的值分别是0和1,但是考虑到==可读性==,才使用这些名字来表示这些常量(宏!)
3.不带缓冲的IO 函数 open、read、write、lseek 以及 close 提供了不带缓冲的1/O。这些函数都使用文件描述符。
#include "apue.h"
#define BUFFSIZE
int
main (void)
{
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"):
exit (0) ;
}
4.标准IO 标准IO函数为那些不带缓冲的IO函数提供了一个带缓冲的接口。使用标准IO函数无序担心如何选取最佳的缓冲区大小,最常用的是printf
#include "apue.h"
int
main (void){
int c;
while ((c = getc(stdin)) |= EOF)
if (putc(c, stdout) == EOF) err_sys ("output error");
if (ferror(stdin))
err_sys ("input error");
exit(0);
}
1.6 程序与进程
1.程序:存储在磁盘上的某个目录中的可执行文件 2.进程:程序的运行实例
3.进程控制: fork, exec, waitpid
4.线程与线程ID
通常一个进程只有一个控制线程(thread),某一时刻执行的执行一组机器指令。也存在多个控制线程的场景,可以充分利用多处理器系统的==并行能力==
一个进程内所有线程共享同一地址空间,文件描述符,栈以及进程相关的属性。访问共享数据时需采取同步措施以避免不一致性
线程也有ID,但只在本线程所在的进程中有效
1.7 出错处理
errno:整形变量errno通常被设置为具有特定信息的值。文件<errno.h>定义了errno以及可以赋予它的各种常量,都以E开头。 例如errno为EACCESS时,表示产生了权限问题
出错打印:
char *strerror(int errnum) errnum为errno的值, 返回该出错字符串的值
void perror(const char *msg) 首先输出msg字符串,然后输出冒号,空格,接着是errno值的出错消息,最后一个是换行符
出错恢复:
致命性错误:无法恢复,顶多只能输出信息,包括 EAGAIN、ENFILE、ENOBUFS、ENOLOCK、ENOSPC、EWOULDBLOCK, ENOMEM(当EBUSY时非致命)。。
非致命性错误:一般可以妥善处理,常用的策略可能是重试(例如网络连接失败)
1.8用户标识
用户id,组id,附属组id
id之所以为整型数,一个是文件存储在磁盘,每个文件都会标明用户id与组id,用整型数省空间,而且整型数比ascii码形式的字符串对比起来更快
1.9信号
信号用于通知进程发生了某种情况。通常有以下三种处理方式:
忽略信号
按系统默认方式处理
提供函数,信号发生时调用,这成为捕捉该信号 很多情况都会产生信号,比如ctrl+c, ctrl+d, kill都能产生信号终止程序
signal函数:捕捉信号。
1.10时间值
UTC时间:自协调世界时,1970年1月1日到某个时刻所经过的秒数
系统基本数据类型time_t保存这种时间
进程时间:
系统基本数据类型clock_t保存这种时间
时钟时间:进程运行的总时间,又称墙上时钟时间(wall clock time)
用户CPU时间:执行用户指令所需的时间
系统CPU时间:进程执行内核程序所经历的时间
1.11 系统调用和库函数
从实现层面而言,系统调用与库函数是有根本区别的。
虽然很多库函数底层是用一个或者多个系统调用实现的,但库函数是可变的,系统调用一般是不可变的。我们可以用系统调用来实现我们自定义库函数。 例如malloc是库函数,底层使用系统调用sbrk,但是我们可以基于sbrk自己设计算法来优化自己的场景下的空间分配
系统调用通常是提供的一个最小接口,而库函数通常提供比较复杂的功能