概述

操作系统的任务是为其上运行的程序服务:包括运行、打开文件、读文件、分配内存以及时间获取。

架构

定义:

In a strict sense, an operating system can be defined as the software that controls the hardware resources of the computer and provides an environment under which programs can run.

UNIX的体系结构

由内而外:
- 内核 其接口被称为系统调用(System call)。
- shell 特殊的应用程序,为其他应用程序提供接口。
- 库函数 在系统调用接口基础上的函数库。
- 应用 就是应用。。

登录

登录方式

系统根据输入的用户名去/etc/passwd目录下检索用户名并找到对应的密码文件进行密码匹配。
密码文件长这样:

sar:x:205:105:Stephen Rago:/home/sar:/bin/ksh

上述对应各字段值类型:
登录名:加密的密码:用户ID:组ID:注释域:起始:shell程序

shell

shell是一个命令解释器,与用户进行交互。

文件与目录

  • 文件系统 树状层级结构
  • 文件名
  • 路径名 创建目录时候自动生成当前目录.和父目录..
  • 工作目录 每个进程都有一个工作目录
  • 起始目录

栗子:ls简要实现

#include "apue.h"
#include <dirent.h>

int main (int argc, char *argv[]) {
    DIR *dp;
    struct dirent *dirp;

    // 输入参数不为2, 暗示使用方法不对
    if (argc != 2) {
        err_quit("usage: ls directory_name");
    } 

    // 打开指定目录失败
    if ((dp = opendir(argv[1])) == NULL) {
        err_sys("can't not open %s", argv[1]);
    }

    // 遍历dp文件夹 打印内部文件的文件名
    while ((dirp = readdir(dp)) != NULL) {
        printf("%s\n", dirp->d_name);
    }

    // 关闭文件夹句柄 && 退出程序
    closedir(dp);
    exit(0);
}

这里可能遇到一个apue.3e的编译问题,解决方案戳这里

命令行编译执行结果如下:

图片描述

输入与输出

文件描述符 (file descriptor)

通常是一个小的非负整数,内核用它标识一个特定进程访问的文件。

标准输入 标准输出 标准错误

一般来说,所有派系的shell都会为程序打开标准输入、标准输出、标准错误三个文件描述符。并且这三个文件描述符都能重定向到某个文件。

ls > file.list

非缓冲的I/O

非缓冲函数如:open, read, write, lseek, close。这些函数都与文件描述符协作。

举个栗子:从标准输入读写入标准输出

#include <stdio.h>
#include <apue.h>

#define BUFFSIZE 4096

int main(void) {
    int n;
    char buf[BUFFSIZE];

    // 头文件<unistd.h>中包含了`STDIN_FILENO`与`STDOUT_FILENO`两个常量
    while ((n = (int)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);
}

命令运行结果截图:
图片描述

标准I/O

标准I/O函数提供一种对不用缓冲I/O函数的带缓冲接口。 无脑说就是不用设置缓冲区大小。比如fgets函数读完一行的长度而read函数需要制定读入的字节数。
我们最熟悉得标准I/O是printf。。。

举个栗子: 用标准I/O将标准输入复制到标准输出

#include <stdio.h>
#include <apue.h>

int main(void) {
    int c;


    // 头文件<unistd.h>中包含了`STDIN_FILENO`与`STDOUT_FILENO`两个常量
    while ((c = getc(stdin)) != EOF) {
        if (putc(c, stdout) == EOF) {
            err_sys("output error");
        }

        if (ferror(stdin)) {
            err_sys("input error");
        }
    }

    exit(0);
}


运行结果略。。

程序和进程

程序

程序是放在磁盘上的。

进程和进程ID

程序执行的实例叫做进程。每个进程都有自己的唯一标识符PID。

栗子:打印进程ID

int main(void) {
    printf("hello world from process ID %d\n", getpid());
    exit(0);
}

运行结果:
图片描述

进程控制

进程控制主要函数有:fork、exec、waitpid

举个栗子:

#include <stdio.h>
#include <sys/wait.h>
#include <apue.h>

int main(void) {
    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("couldn't execute: %s", buf);
            exit(127);
        }

        if ((pid = waitpid(pid, &status, 0)) < 0) {
            err_sys("watipid error");
        }

        printf("%% ");
    }

    exit(0);
}

运行结果:
图片描述

线程和线程ID

通常一个进程只有一个控制线程,同一时刻只能执行一组机器指令。
在同一个进程内得线程共享同一个地址空间、文件描述符、栈以及进程相关属性。
因为共享资源的关系,所以这里有个线程安全的概念。
与进程相同,线程也用ID标识。线程ID只在进程中起作用。出了进程就没有意义了。

出错处理

UNIX函数出错的时候通常返回一个负值,用errno表示错误得种类。某些函数不返回负值而是使用另外一种约定,比如返回一个只想对象的指针的大多数函数,出错时候返回null指针。

使用errno的两条规则:

  1. 如果没有出错,则errno值不会被例行清除。只有当函数返回值错误的时候才去检查其值。
  2. 没有函数会将errno值设置为0,在<errno.h>中定义的所有常量都不为0。

标准C钟定义了下面两个方程来实现打印错误消息。

char *strerror(int errnum);
void perror(const char *msg);

第一个函数表明根据errno找到错误信息字符串指针,第二个函数表明根据指针,打印出错误信息。

举个栗子:

#include <string.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    fprintf(stderr, "EACCES: %s\n", strerror(EACCES));
    errno = ENOENT;
    perror(argv[0]);
    exit(0);
}

通过将./a.out传入perror函数,这样可以在管道中知道是哪个程序出错了。

错误恢复

<errno.h>的错误定义划分为两种:致命与非致命。致命错误没有恢复操作。而非致命错误相反,遇到此类错误时候可以更为妥善的处理。
资源相关的非致命错误包含EAGAIN, ENFILE, ENOBUFS, ENOLCK, ENOSPC, EWOULDBLOCK,有些时候还有ENOMEMEBUSY问题出在共享资源被占用的时候也可以视为非致命错误。
资源相关非致命错误的恢复策略一般是延迟重试。
适时的采用恢复策略可以增加应用的健壮性从而避免异常退出。

用户标识

用户ID

用户ID用来让系统区分不同的用户。
用户ID为0得用户为超级用户。操作系统的很多权限只向超级用户提供。

组ID

组ID是系统管理员在指定用户名提供的。这种机制允许组内成员共享资源。
组文件将组名映射为数字组ID,它通常是/etc/group.

举个栗子:

int main(int argc, char *argv[]) {
    printf("uid = %d, gid = %d\n", getuid(), getgid());
    exit(0);
}

附加组ID

大多数UNIX版本还允许一个用户属于另外一个用户组。

信号

信号是通知进程已经发生某种情况的一种技术。举个栗子,一个进程在执行除法运算操作,除数为0,则将名为SIGFPE的信号发给该进程。

栗子:
为了让程序能捕捉到信号,需要让其调用signal函数来指定对应信号应当执行的动作(函数)。下栗是在捕获信号时候进行打印输出:

// 声明信号handler
static void sig_int(int);

// 简单handler实体 - 打印
void sig_int(int signo) {
    printf("interrupt\n%% ");
}

时间值

UNIX系统有两类时间值:

  • 日历时间
  • 进程时间

日历时间:广义时间,用time_t修饰。
进程时间:CPU时间,用来度量进程使用的CPU资源,用clock_t修饰。进程时间又分为:

  • 时钟时间 进程运行的时间总量。与总进程数有关。
  • 用户CPU时间 CPU执行用户指令所用的时间
  • 系统CPU时间 CPU执行内和程序所用的时间

用户CPU时间与系统CPU时间统称CPU时间。可以通过time函数来获取时间值组成。

系统调用和库函数(理论)

从实现者角度看,系统调用和库函数之间有重大区别,但从用户角度,区别并不是很重要。

应用程序可以调用系统函数或者库函数,而很多库函数会调用系统调用。
系统调用通常提供最小接口,而库函数提供比较复杂的功能。

总结

UNIX概述


Cruise_Chan
729 声望71 粉丝

技能树点歪了...咋办


引用和评论

0 条评论