0 环境配置
- 下载:到官网 http://www.apuebook.com/code3e.html 下载 ‘src.3e.tar.gz’
- 解压:$ tar -zvx -f src.3e.tar.gz
- 安装libbsd:$ sudo apt-get install libbsd-dev
- 进入apue.3e $ make
- 将/apue.3e/include/apue.h文件拷贝到/usr/include/
- 将/lib/libapue.a文件拷贝到/usr/lib
1 引言
- 所有的操作系统都是为它们所运行的程序提供服务,如:执行新程序,打开文件,读文件,分配存储区等.
2 Unix体系结构
- 操作系统可以定义为一个软件,控制计算机的硬件资源,通常称为内核。
- 内核的接口–系统调用
3 登录
- 登录名
- shell
- POSIX 表示可移植操作系统接口(Portable Operating System Interface ,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准,是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称.
4 文件和目录
- Unix文件系统是目录和文件的一种层次结构,所有东西的起点都是成为根目录。
- 目录是包含目录项的文件。逻辑上一个目录项都包含一个文件名,文件属性等信息。
- 目录中的各个名字成为文件名
5 输入和输出
- 文件描述符:一个小的非负整数,当代一个现有文件或者创建一个新文件时,都会返回一个文件描述符。
- 当运行一个新程序时,所有的shell都会打开三个文件描述符:标准输入,标准输出,标准错误。这三个文件描述符都可以定向到某个文件
- 不带缓存的I/O:open,read,write,lseek,close。这些函数都能使用文件描述符。
将标准输入复制到标准输出上:
#include "apue.h"
#define BUFFESIZE 4096
int main(void)
{
int n;
char buf[BUFFESIZE];
while ((n = read(STDIN_FILENO, buf, BUFFESIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if (n < 0)
err_sys("read error");
exit(0);
}
- 标准I/O:为那些不带缓存的I/O函数提供了一个带缓冲的接口。使用标准I/O时,无须担心如何选择最佳缓冲区大小。
将标准输入复制到标准输出,使用标准i/o
#include<stdio.h>
#include "apue.h"
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");
exit(0);
}
6 程序和进程
- 程序是一个存储在磁盘的可执行文件,内核使用exec函数将程序读入内存,并执行
- 进程:程序执行的实例称为进程,每个进程都有一个数字标识符称为进程ID(process ID),为非负整数.
打印进程ID:
#include "apue.h"
int main(void)
{
printf("hello world from process ID %ld\n", (long)getpid());
exit(0);
}
output
//hello world from process ID 18910
- 进程控制:主要函数fork, exec,waitpid
#include "apue.h"
#include <sys/wait.h>
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);
exit(127);
}
if ((pid = waitpid(pid, &status, 0)) < 0)
err_sys("waitpid error");
printf("%% ");
}
exit(0);
}
- execlp函数要求的参数是以null结尾,故要替换buf的最后一个字符,在子进程中,调用execlp执行从标准输入读入命令
通常一个进程只有一个控制线程,但是对于某些问题,如果多个线程分别作用于它的不同部分,那么解决起来容易很多。
线程也有ID标识,但是只在它所属的进程内起作用
7 出错处理
- unix系统函数出错时,会返回一个负值。整形变量通常被设置为一个特定信息的值。
#include <string.h>
char* strerror(int errnum);
- perror函数基于errno的当前值,在标准错误上产生一个出错消息然后返回。
#include <stdio.h>
void perror(const char* msg);
- 使用这两个出错函数的例子:
#include "apue.h"
#include <errno.h>
int main(int argc, char* argv[])
{
fprintf(stderr, "EACCES: %s\n", strerror(EACCES));
errno = ENOENT;
perror(argv[0]);
exit(0);
}
//EACCES: Permission denied
- 错误分为:致命错误和非致命错误。对于非致命错误,可以妥善处理。常用的方法是延迟一段时间。
8 用户标识
- 用户ID:每个用户都有一个ID。ID为0的用户为root或者superuser
- 组ID:用户所在group的ID
- 将用户名和组名映射成ID,然后比较。
9 信号
- 信号用于通知进程发生了某种情况。例如,若某一进程执行除法操作,其除数为0,则将名为SIGFPE的信号发送给该进程。有以下3种处理方式:
- 忽略;
- 按系统默认方式处理;
- 提供一个函数,信号发生时调用该函数,这被成为捕捉该信号。
- 键盘产生两种信号:Ctrl+c(中断信号)和Ctrl+(退出信号)
- 当向一个进程发信号时,我们必须是那个进程的所有者或者是超级用户。
10 时间值
- 日历时间:UTC 1970年1月1日 00:00:00 这个时间以来经过的秒数。time_t用于保存这个时间值
- 进程时间:也称CPU时间,以时钟滴答计算,每秒滴答声可以有一定的取值如50,60,100。clock_t保存这个值。
- UNIX系统为一个进程维护了3个进程时间值:
- 时钟时间:进程运行的时间总量
- 用户CPU时间:执行用户指令用的时间
- 系统CPU时间:执行系统调用所用的时间
11系统调用和库函数
系统调用实际上就是指最底层的一个调用,在linux程序设计里面就是底层调用的意思。面向的是硬件。而库函数调用则面向的是应用开发的,相当于应用程序的api,采用这样的方式有很多种原因,第一:双缓冲技术的实现。第二,可移植性。第三,底层调用本身的一些性能方面的缺陷。第四:让api也可以有了级别和专门的工作面向。
- 系统调用通常用于底层文件访问(low-level file access),例如在驱动程序中对设备文件的直接访问。
- 系统调用是操作系统相关的,因此一般没有跨操作系统的可移植性。
- 系统调用发生在内核空间,因此如果在用户空间的一般应用程序中使用系统调用来进行文件操作,会有用户空间到内核空间切换的开销。
- 库函数调用通常用于应用程序中对一般文件的访问。
库函数调用是系统无关的,因此可移植性好。
由于库函数调用是基于C库的,因此也就不可能用于内核空间的驱动程序中对设备的操作。
系统调用由操作系统内核提供,运行于内核核心态,而普通的库函数调用由函数库或用户自己提供,运行于用户态。