APUE 第一章 Unix基础知识

241 阅读9分钟

1.1 引言

1.2 UNIX 体系结构

从严格意义上来说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境

通常将这种软件称为内核

内核的接口被称为系统调用

公用函数库构建在系统调用接口之上。

应用程序既可以使用公用函数库,也可以使用系统调用。

shell是一个特殊的应用程序,为运行其他应用程序提供了一个接口。

广义上来说,操作系统包括内核和一些其他软件(系统应用程序,shell,公用函数库等)。

LinuxGNU操作系统使用的内核。

image.png

1.3 登录

1. 登录名

/etc/passwd口令文件中可以查看登录名。

依次是:登录名,加密口令,数字用户ID,数字组ID,注释字段,其实目录,shell程序。

image.png

2. shell

shell是一个命令行解释器,它读取用户输入,然后执行命令。

1.4 文件和目录

1. 文件系统

UNIX文件系统是目录和文件的一种层次结构,所有东西的起点是目录,这个目录的名称是一个字符\

目录是一个包含目录项的文件。

在逻辑上,可以认为每个目录项都包含一个文件名,同时还包含说明该文件属性的信息(具体实现并非如此)。

2. 文件名

创建新目录时,会自动创建两个文件名:.(称为点),..(称为点点)。

点指向当前目录,点点指向父目录。

在最高层次的根目录中,点点与点相同。

3. 路径名

相对路径名,绝对路径名。

#include <apue.h>
#include <dirent.h>
int 
main(int argc, char *argv[])
{
    DIR *dp;
    struct dirent *dirp;
    if(argc != 2)
        err_quit(" ");
    if((dp = opendir(argv[1])) == NULL)
        err_sys("  ");
    while((dirp = readdir(dp)) != NULL)
        printf("%s\n", dirp->d_name);
    closedir(dp);
    exit(0);
}

image.png

4. 工作目录

所有相对路径名都是从工作目录开始解释的。

5. 起始目录

1.5 输入和输出

1. 文件描述符

文件描述符通常是一个小的非负整数,内核用以标记一个特定进程正在访问的文件。

2.标准输入,标准输出和标准错误

每当运行一个新程序时,所有shell都为其打开3个文件描述符。

如果不做特殊处理,则这三个描述符都链接向终端。

3. 不带缓冲I/O

函数open,read,write,lseek以及close提供不带缓冲的I/O。

COPY FROM : m.yisu.com/zixun/19489…


首先要明白不带缓冲的概念:所谓不带缓冲,并不是指内核不提供缓冲,而是只单纯的系统调用,不是函数库的调用。系统内核对磁盘的读写都会提供一个块缓冲(在有些地方也被称为内核高速缓存),当用write函数对其写数据时,直接调用系统调用,将数据写入到块缓冲进行排队,当块缓冲达到一定的量时,才会把数据写入磁盘。因此所谓的不带缓冲的I/O是指进程不提供缓冲功能(但内核还是提供缓冲的)。每调用一次write或read函数,直接系统调用。

而带缓冲的I/O是指进程对输入输出流进行了改进,提供了一个流缓冲,当用fwrite函数网磁盘写数据时,先把数据写入流缓冲区中,当达到一定条件,比如流缓冲区满了,或刷新流缓冲,这时候才会把数据一次送往内核提供的块缓冲,再经块缓冲写入磁盘。(双重缓冲)

因此,带缓冲的I/O在往磁盘写入相同的数据量时,会比不带缓冲的I/O调用系统调用的次数要少。


4. 标准I/O

标准I/O函数为那些不带缓冲的I/O函数提供了一个带缓冲的接口。

最熟悉的标准I/O函数是printf

1.6 程序和进程

1. 程序

程序是一个存储在磁盘上某个目录中的可执行文件。 内核使用exec函数,将程序读入内存,并执行程序。

2. 进程和进程ID

pid_t getpid();

3. 进程控制

forkexecwaitpid

4. 线程和线程ID

这里APUE有误吗?书上写的线程会共享栈,存疑。

COPY FROM:www.nowcoder.com/questionTer…


线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。


1.7 出错处理

当UNIX系统函数出错时,通常会返回一个负值,而且整型变量errno通常被设置为具有特定信息的值。

C标准定义了两个函数,他们用来打印出错信息。

#include <string.h>
char *strerror(int errnum);

将errnum映射为一个出错消息字符串,并返回此串的指针。

#include <stdio.h>
void perror(const char *msg);

它首先输出由msg指向的字符串,然后是一个冒号,一个空格,接着是对应于errno值的出错消息,最后是一个换行符。

出错恢复

出错分为两类:致命性和非致命性。

对于致命性,无法执行恢复动作。

对于非致命性:妥善处理。

对于资源相关的非致命性错误的典型恢复操作是延迟一段时间,然后重试。

1.8 用户标识

1. 用户ID

ID为0的是超级用户,根用户。

2. 组ID

/etc/group

3. 附属组ID

每个用户属于最多16个其他的组

1.9 信号

信号用于通知进程发送了某种情况。

进程有以下3中处理信号的方法:

  1. 忽略。
  2. 按系统默认方式处理。
  3. 提供一个函数,信号发生时调用该函数,这被称为捕捉该函数。

1.10 时间值

  1. 日历时间

time_t

  1. 进程时间 clock_t

当度量一个进程的执行时间时,UNIX系统为一个进程维护了3个进程时间值:

  1. 时钟时间
  2. 用户CPU时间
  3. 系统CPU时间

1.11 系统调用与库函数

所有的操作系统都提供多种服务的入口点,由此程序向内核请求服务。 各种版本的UNIX实现都提供良好定义的,数量有限,直接进入内核的入口点,这些入口点被称为系统调用

系统调用接口总是在《UNIX程序员手册》的第二部分中说明,由C语言定义的,与具体系统如何调用一个系统调用的实现技术无关。

UNIX是为每个系统调用在标准C库中设置一个同样名字的函数。

用户进程用标准C调用序列来调用这些函数,然后,函数又用系统所要求的技术调用相应的内核服务。

《UNIX程序员手册》第三部分定义了程序员可以使用的通用库函数。虽然这些函数可能会调用一个或多个内核的系统调用,但是他们并不是内核的入口点。

从实现者角度来看,系统调用和库函数之间有根本的差别,但从用户角度来看,其区别并不重要。

在本书中,系统调用和库函数都是以C函数形式出现,两者都为应用程序提供服务。

习题

COPY FROM github.com/Tianpingan/…

1. 在系统上验证,除根目录外,目录 . 和目录 .. 是不同的

用指令ls -ai就能列出.和..的inode。

image.png

2. 分析1.6.2.c的输出,说明进程 ID 为 852 和 853 的进程发生了什么情况

在执行这两次程序的间隙中,系统产生了ID为852和853的其他进程,所以第二次运行进程ID为854。

3. 在1.7.c中, perror 的参数是用 ISO C 的属性 const 定义的,而 strerror 的整形参数没有用此属性定义,为什么?

下面是两个函数的定义:

#include <string.h>
char *strerror(int errnum);

#include <stdio.h>
void perror(const char *msg);

strerror 传递的参数为 int 类型,将会在对应的函数内复制一份,因此不会修改原来参数的值

perror 传递的参数为 const char * 类型,传递的是对应的地址,如果不使用 const 限定的话,那么如果在函数内修改了值,函数外的变量值也会变化。


COPY FROM : bbs.csdn.net/topics/1048…

const char *的作用:定义的字符串不能被改变,但是指针可以改变 指针内容不能改变

1 指向变量的指针变量:
出身: int * point;
遭遇: 两人都另有新欢
心声: 你可以变心,我也可以爱别人,很公平。

2 指向常量的指针变量:
出身: const int * point;
遭遇: 抛弃对方
心声: 说过多少回了,我们之间已经结束了,你别再缠着我了,告诉你,我已经有了新的女朋友了

3 指向变量的指针常量:
出身: int * const point;
遭遇: 被对方抛弃
心声: 你怎么可以这样呢,你知道我的心里永远只有你一个,你怎么可以爱上别人呢

4 指向常量的指针常量:
出身: const int * const point;
遭遇: 两情相悦,天长地久
心声: 你是我的唯一,我是你的永远,钻石恒久远,怎即我俩情

怎么判断:主要是看const的位置来确定,如果在" * "画一条竖线,const在竖线左边即所指的目标为常量,如果在竖线右边即指针本身是常量,两边都有就意味着都是常量.


4. 若日历时间存放在带符号的 32 位整型数中,那么到哪一年它将溢出?可以用什么办法扩展溢出浮点数?采用的策略是否与现有的应用相兼容?

32 位整型数的最大值为 2147483647 ,大概为 68.09626 年。通过查询可知,最大可表示到 2038-01-19 11:14:07

使用 IEEE754 编码扩展(double),与现有的应用不一定兼容。

5. 若进程时间存放在带符号的 32 位整型数中,而且每秒为 100 时钟滴答,那么经过多少天后该时间值将会溢出?