Unix基础

来源:互联网 发布:js处理遍历json数据 编辑:程序博客网 时间:2024/04/28 10:51

概述

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

架构

定义:

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 4096int 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函数来指定对应信号应当执行的动作(函数)。下栗是在捕获信号时候进行打印输出:

// 声明信号handlerstatic 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概述

0 0