Unix 基础知识
@[执行新程序, 打开文件, 读取文件, 分配存储区, 获取当前时间等, 应用程序, shell, 系统调用, 公共函数库, 内核]
Unix 体系结构
![Unix体系结构图]](https://img.haomeiwen.com/i1678197/bc94cb3aed5365d4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
@[应用程序|shell|系统调用|公共函数库|内核|]
登陆
登陆名
系统口令文件在(/etc/passwd)文件中查看登陆名
口令文件中的登陆项由7个以冒号分割的字段组成
依次是:
1:登陆名
2:加密口令
3:数子用户ID(UID)
4:数子组ID(GID)
5:注释字段
6:起始目录
7:shell程序
如下图红箭头所示:
口令登陆项说明
shell
shell 是一个命令行解释器,它读取用户输入,然后执行命令。shell的用户输入通常来自于终端(交互式shell),有时则来自于文件(称为shell 脚本)
文件和目录
1:文件系统
Unix文件系统是目录和文件的一种层次结构,所有东西的起点都称为root的目录,这个目录的名称是一个字符“/”。
stat和fstat函数返回包括所有文件属性的一个信息结构
目录项的逻辑视图与实际存放在磁盘的方式是不同的。unix文件系统的大多数实现并在目录项存放属性,这是应为当一个文件具有多个硬链接时,很难保持多个属性副本之间的同步.
2:文件名
目录中的各个名字称之为文件名(filename)。
创建新目录时会自动创建了两个文件名:.(称为点)和..(称为点点)。点指当前目录,点点指向父目录。在最高层次的根目录中,点点与点相同。
3:路径名
由斜线分割的一个或多个文件名称组成的序列(也可以斜线开头)构成路径名,以斜线开头的路径名为绝对路径名,否则称为相对路径名称。相对路径名指向相对于当前目录的文件。文件系统根的名字(/)时一个特殊的绝对路径名,它不包含文件名
实例
/*
============================================================================
Name : file_workspace.c
Author : james
Version :
Copyright : Your copyright notice
Description : Hello World in C, Ansi-style
============================================================================
*/
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
int main(int argc, char *argv[]) {
DIR *dp;
struct dirent *dirp;
if(argc != 2){
perror("us age: is directory_name");
}
if((dp = opendir(argv[1])) == NULL){// 打开dir 返回dir句柄
printf("can't open %s",argv[1]);
}
while((dirp = readdir(dp)) != NULL){// 循环读取dir 并打印出来
printf("%s\n",dirp->d_name);
}
closedir(dp);// 关闭当前目录文件
return EXIT_SUCCESS;
}
4:工作目录
每一个进程都有一个工作目录(working directory),有时成其为当前工作目录(current working directory),所有相对路径都从工工作目录开始解释。进程可以用chdir函数更改起工作目录的。
5:起始目录
登陆时,工作目录设置为起始目录(home directory),该起始目录从口令文件中相应用户登陆项获得
输入和输出
1:文件描述符
文件描述符(file descriptor)通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件,当内核打开或创建一个新的文件时,他都会返回一个文件描述符。在读在写时,可以使用这个文件描述符
2:标准输入、标准输出和标准错误
按惯例,每当运行一个新程序时,所有的shell都为其打开3个文件描述符,即标准输入、标准输出、标准错误。
3:不带缓冲的I/O
函数open、read、write、lseek以及close提供了不带缓冲的IO,这些函数都使用文件描述符。
实例:
/*
* file_rw.c
*
* Created on: Mar 13, 2016
* Author: james
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "file_rw.h"
#include "log.h"
void file_rw(){
int n;
char buf[BUFFSIZE];
while((n = read(STDIN_FILENO,buf,BUFFSIZE)) > 0){
if(write(STDOUT_FILENO,buf,n) != n){
log_error("write error");
}
}
if(n <0){
log_error("read error");
}
}
两个常量STDIN_FILENO和STDOUT_FILENO 定义在unistd.h文件中,它们制定了标准输入和标准输出的文件描述符。在POSIX标准中,它们的值分别是0和1 ,但是考虑到可读性,我们将使用这些名字来表示这些变量
4:标准IO
标准IO函数为那些不带缓冲的IO函数提供了一个带缓冲的接口。使用标准IO函数无需担心如何选取最佳缓冲区大小,使用标准IO函数还简化了对输入行的处理。标准IO函数库提供了使我们能够控制该库索使用的缓冲风格的函数。
实例
/*
* get_put_c.c
*
* Created on: Mar 13, 2016
* Author: james
*/
#include <stdio.h>
#include <stdlib.h>
#include "get_put_c.h"
void get_put_c(){
int c;
while((c = getc(stdin)) !=EOF){
if(putc(c,stdout) == EOF){
log_error("out put error");
}
}
if(ferror(stdin)){
log_error("input error");
}
}
标准IO常量stdin 和 stdout 也在头文件<stdio.h>文件定义,它们分别表示标准输出和标准输入
程序和进程
1:程序:
程序(program)是一个存储在磁盘上某个目录中的可比性文件。内核使用exec函数,将程序读入到内存,并执行程序
2:进程和进程ID
程序的执行实例被称之为进程(process)。
Unix 系统确保每个进程都有一个唯一的数字标识符,称为进程ID(process ID),进程ID总是一个非负整数
3:进程控制
有三个用于进程控制的主要函数 fork exec 和waitpid(exec 函数有7中变体,但经常把它们统称为exec函数)
实例
/*
* process.c
*
* Created on: Mar 13, 2016
* Author: james
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include "process.h"
void process_fork() {
char buf[MAXLINE];
pid_t pid;
int status;
log_info("%% ");
while (fgets(buf, MAXLINE, stdin) != NULL) {
if (buf[strlen(buf) - 1] == '\n') {
buf[strlen(buf) - 1] = 0;
}
if ((pid = fork()) < 0) {
log_error("fork error");
} else if (pid == 0) {
execlp(buf, buf, (char *) 0);
log_error("execute error");
exit(127);
}
if (pid == waitpid(pid, &status, 0) < 0) {
log_error("waitpid error");
}
log_info("%% ");
}
}
^D 表示一个控制字符,控制字符是特殊字符,其构成方法是:在键盘上按下控制键--统称被标记为control或ctrl 同时按另一个键。^D 默认是文件结束符。
4:线程和线程ID
通常,一个进程只有一个控制线程——某一时刻执行的一组机器指令。对于某些问题,如果有多个控制线程分别作用于它的不同部分,那么解决起来就容易得多。另外,多个控制线程也可以充分利用多处理器系统的并行能力。
一个进程内的所有线程共享同一个地址空间,文件描述符、栈以及进程相关的属性。
与进程相同,线程也有ID标识。但是,线程ID只在他所属的进程内起作用,一个进程中的线程ID在另一个进程中没有意义。
出错处理
当Unix系统函数出错时,通常会返回一个负值,而且整型变量errno通常设置为具有特定信息的值。
文件<errno.h>中定义了errno以及可以赋与它的各种常量。这种常量通常以E开头
多个线程共享进程地址空间,每个线程都有属于它自己的局部errno,以避免一个线程干扰另一个线程。
对于errno应当注意的两条规则:
1:第一条规则 : 如果没有出错,其值不会被例程清除。因此,仅当函数的返回值指明错误时,才检验其值。
2:第二条规则 : 任何函数都不会将errno值设为0,而且<errno.h>中定义的所有常量都不为0
C标准定义了两个函数,它们用于打印错误信息
#include <string.h>
char *strerror(int errnum)
strerror 函数将errnum(通常就是errno值)映射为一个出错消息字符串,并且返回此字符串的指针
perror函数基于errno的当前值,在标准错误上产生一条出错消息,然后返回。
#include <stdio.h>
void perror(const char *name);
他首先输出由msg指向的字符串,然后是一个冒号,一个空格,接着是对应于errno值出错消息,最后是一个换行符
出错恢复
可将在<errno.h>文件中定义的各种出错分成两类,致命性的和非致命性的。
对于致命性的错误,无法执行恢复动作,最多能做的是在用户屏幕上打印出一条错误消息或者将一条错误消息写入日志,然后退出。
对非致命的错误,有时可以妥善第进行处理。
用户标识
1:用户ID
口令文件登陆项中的用户ID(userID)是一个数值,它向系统标识各个不同的用户。系统管理员再确定一个用户登录名的同时,确定其用户ID,用户不能更改其他用户ID,通常每个用户有一个唯一的用户ID。
用户ID为0的用户为根用户(root)或超级用户(superuser)。在口令文件,通常有一个登陆项,其登录名为root,我们称这种用户的特权为超级用户特权。
2:组ID
口令文件登陆项也包括用户的组ID(groupID),它是一个数值,组ID也是由系统管理员在指定用户登陆名时分配的。一般来说,在口令文件中有多个登陆项具有相同的组ID。
组文件将组名映射为数值的组ID。组文件通常是/etc/group
使用数值的用户ID和数值的组ID设置权限是历史上形成的,对于磁盘上的每个文件,文件系统都存储该文件所有者的用户ID和组ID。存储这两个值只需4个字节。
3:附属组ID
除了口令文件中对一个登陆名制定一个组ID外,大多数Unix系统版本还允许一个用户属于另外一些组。
信号
信号(signal)用于通知进程发生了某种情况。
例如:
若某一进程执行除法操作,其除数为0,则将名为SIGFPE(浮点异常)的型号发送给该进程。
1: 忽略信号。
2: 按照默认方式处理。
3: 提供函数,信号发生时调用该函数,这被称为捕捉该信号。通过提供自编的函数我们就能知道什么时候产生了信号,并按期望的方式处理它。
很多情况都会产生信号,终端键盘上有两种信号的方法,分别称为中断键和退出键,它们被用于终端当前运行的进程。
另一种产生信号的方法是调用kill函数,在进程中调用此函数就可向另一个进程发送一个信号。
实例
/*
============================================================================
Name : signal_demo.c
Author : james
Version :
Copyright : Your copyright notice
Description : Hello World in C, Ansi-style
============================================================================
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
#define MAXLINE 4096
static void signal_init(int signo);
int main(void) {
char buf[MAXLINE];
pid_t pid;
int status;
if(signal(SIGINT,signal_init) == SIG_ERR){
strerror("signal error");
}
printf("%% ");
while(fgets(buf,MAXLINE,stdin) != NULL){
if(buf[strlen(buf)-1] == '\n'){
buf[strlen(buf)-1] = 0;
}
if((pid = fork()) < 0){
strerror("fork error");
}else if (pid == 0){
setsid();
execlp(buf,buf,(char *)0);
perror("couldn't execute error");
exit(127);
}
if(pid == waitpid(pid,&status,0) < 0){
perror("waitpid error");
printf("%% ");
}
}
return EXIT_SUCCESS;
}
void signal_init(int signo){
printf("interrupt\n %%");
}
时间值
历史上,Unix系统使用过两种不同的时间值。
1:日历时间 ,该值是自协调世界时。
2:进程时间,被称为CPU时间,用以度量进程使用的中央处理器资源,进程时间以时钟滴答计算,每秒钟曾经取为50、60、100个时钟滴答
系统的基本数据类型clock_t 保存这种时间值,
当度量个进程的执行时间,unix系统为一个进程维护了3个进程时间值:
-时钟时间
-用户cpu时间
-系统cpu时间
系统调用和库函数
所有的操作系统都提供了多种服务的入口点,由此呈现向内核请求服务,各种版本的unix 实现都提供好了良好定义、数量有限、直接进入内核的入口点被称为系统调用。
网友评论