1.1 Unix体系
内核的接口被称为系统调用。公用函数库构建在系统调用接口之上。shell和公共函数都能直接进行系统调用,
应用程序可以直接进行系统调用,也可以使用shell和公共函数库里的函数,从而间接进行系统调用。
1.2 登录
shell是一个命令行解释器,它读取用户输入然后执行命令。shell的用户输入通常来自于终端(交互式shell),
有时则来自于文件(称为shell脚本)。
1.3 文件和目录
除了斜线(/)和空格之外,都可以用于文件名;由斜线分隔的多个文件名构成路径名。
#include <stdio.h>
#include "apue.h"
#include <dirent.h>
int main(int argc, char *argv[]) {
DIR *dp;
struct dirent *dirp;
if (argc != 2) {
err_quit("usage: ls directory name");
}
if ((dp = opendir(argv[1])) == NULL) {
err_sys("cannot open %s", argv[1]);
}
while ((dirp = readdir(dp)) != NULL) {
printf("%s\n", dirp->d_name);
}
closedir(dp);
return 0;
}
1.4 输入输出
每当运行一个新程序的时候,所有的shell都会为其打开三个文件描述符,即标准输入、标准输出和标准错误.
函数open、 read、 write、 lseek以及close提供了不带缓冲的I/O。 这些函数都使用文件描述符。
按照字节读取和写入,借助于缓冲区。标准I/O函数为那些不带缓冲的I/O函数提供了一个带缓冲的接口。
#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.5 程序和进程
程序(program)是一个存储在磁盘上某个目录中的可执行文件。内核使用exec函数将程序读入内存并执行程序。
UNIX系统确保每个进程都有一个唯一的数字标识符,称为进程ID,进程ID总是一个非负整数。
#include "apue.h"
int main(void) {
printf("hello world from process with id %d", getpid());
exit(0);
}
有三个用户进程控制的主要函数:fork、exec和waitpid。
#include "apue.h"
#define BUFFSIZE 1024
int main(void)
{
char buf[BUFFSIZE];
pid_t pid;
int status;
printf("%% ");
while ((fgets(buf, BUFFSIZE, 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 ) {
/*
* int execlp(const char * file,const char * arg,....);
* 传入参数可执行文件名,会从PATH路径下去寻找,agr0和file同名,最后一个参数必须是NULL
*/
execlp(buf, buf, NULL);
err_ret("exec %s error", buf);
exit(127);
}
//父进程等待子进程退出
if ((pid = waitpid(pid, &status, 0)) < 0)
err_sys("wait error");
printf("%% ");
}
exit(0);
}
1.6 出错处理
当UNIX系统函数出错时通常会返回一个负值,而且整型变量errno通常被设置为具有特定信息的值。errno可以是一个整形或是一个函数指针。
对于errno,如果没有出错其值不会被清除。仅当函数的返回值指明出错时才检验其值。任何函数都不会将errno值设置为0,而且在<errno.h>中定义的所有常量都不为0。
致命性错误:无法恢复
非致命错误:通常是临时错误,比如资源问题
#include "apue.h"
#include <errno.h>
int main(int argc, char*argv[]) {
printf("EGAIN: %s\n", strerror(EAGAIN));
errno = EAGAIN;
perror(argv[0]);
}
1.7 用户标识
用户ID为0的用户为root用户.
#include "apue.h"
int main(int argc, char*argv[]) {
printf("getuid: %d, getgid: %d\n", getuid(), getgid());
}
1.8 信号
信号通知进程发生了事件。进程处理信号的方式:
- 忽略, 2. 按照系统默认方式, 3.调用预设函数
#include "apue.h"
#include <sys/wait.h>
#define BUFFSIZE 1024
static void sig_int(int);
int main(void)
{
char buf[BUFFSIZE];
pid_t pid;
int status;
if (signal(SIGINT, sig_int) == SIG_ERR) //设置SIGINT信号处理函数
err_sys("signal error");
printf("%% ");
while ((fgets(buf, BUFFSIZE, 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 ) {
/*
* int execlp(const char * file,const char * arg,....);
* 传入参数可执行文件名,会从PATH路径下去寻找,agr0和file同名,最后一个参数必须是NULL
*/
execlp(buf, buf, NULL);
err_ret("exec %s error", buf);
exit(127);
}
//父进程等待子进程退出
if ((pid = waitpid(pid, &status, 0)) < 0)
err_sys("wait error");
printf("%% ");
}
exit(0);
}
void sig_int(int sig) {
printf("interuppted by %d\n%%", sig);
}
1.9 时间
进程时间包括:
时钟时间又称为墙上时钟时间(wall clock time),它是进程运行的时间总量。
用户CPU时间是执行用户指令所用的时间量。系统CPU时间是为该进程执行内核程序所经历的时间。
1.10 系统调用
在Unix中为每个系统调用在标准C库中设置了同名的函数。用户进程通过标准C程序来调用这些函数,然后函数又调用相应的内核服务。
从实现者的角度来看,系统调用和库函数之间有根本的区别,但从用户角度来看其区别并不重要。
系统调用提供最小接口,而库函数可以更复杂。