美文网首页
UNIX基础知识

UNIX基础知识

作者: yuq329 | 来源:发表于2020-06-01 01:03 被阅读0次

    UNIX基础知识

    • UNIX体系结构
    • 登陆
    • 文件和目录
      • 文件系统【1、目录是一个包含目录项的文件;2、根目录:所有东西的起点;】
        • 文件属性:statfstat返回包含所有文件属性的一个信息结构;
      • 文件名
        • 斜线/和空字符不能出现在文件名中,斜线用来分隔构成路径名的各文件名,空字符用来终止一个路径名
        • 两个特殊文件.(点)和..(点点),前者指向当前目录,后者指向父目录
      • 路径名【绝对路径以/开头,相对路径以.开头】
          #include<dirent.h>
          #include<apue.h>
          #include <error.h>
          
          int dir(char *path) {
              DIR *dp;
              struct dirent *dirp;
          
              if (path == NULL)
                  err_quit("usage: directory_name not null");
          
              if ((dp = opendir(path)) == NULL)
                  err_sys("can't open %s", path);
          
          
              while ((dirp = readdir(dp)) != NULL)
                  printf("%s\n", dirp->d_name);
          
              closedir(dp);
              return 0;
          }
          
          int main(int argc, char *argv[]) {
              char *path = "..";
              return dir(path);
          }
      
      此程序读取一个文件夹并输出文件夹下的文件及目录信息,其核心代码可以简略如下:
          DIR *dp;//目录
          struct dirent *dirp;//文件或者目录文件结构
          dp = opendir(path);//打开目录
          while ((dirp = readdir(dp)) != NULL)//不断读取目录
              printf("%s\n", dirp->d_name);//输出文件名字
          closedir(dp);//结束读取之后关闭目录
      
      可以看一下strut dirent
      #if __DARWIN_64_BIT_INO_T
      struct dirent __DARWIN_STRUCT_DIRENTRY;
      #endif /* __DARWIN_64_BIT_INO_T */
      #define __DARWIN_STRUCT_DIRENTRY { \
          __uint64_t  d_ino;      /* file number of entry */ \
          __uint64_t  d_seekoff;  /* seek offset (optional, used by servers) */ \
          __uint16_t  d_reclen;   /* length of this record */ \
          __uint16_t  d_namlen;   /* length of string in d_name */ \
          __uint8_t   d_type;     /* file type, see below */ \
          char      d_name[__DARWIN_MAXPATHLEN]; /* entry name (up to MAXPATHLEN         bytes) */ \
      }
      
      • 工作目录
        • 每个进程都有一个工作目录:当前工作目录,相对路径从工作目录开始解释。
        • 可以使用chdir函数改变当前工作目录。
      • 起始目录
        • 用户登陆时的工作目录设置为起始目录,起始目录在登陆口令文件中定义。
    • 输入和输出
      • 文件描述符

        • 一个小的非负整数,内核用于标识一个特定进程正在访问的文件
        • 当内核打开一个文件时或者创建一个文件时,它都返回一个文件描述符
      • 标准输入、标准输出和标准错误

        • 当运行一个新程序时,所有的shell都为其打开3个文件描述符,标准输入标准输出标准错误
        • 3个文件描述符都可重定向到文件(详细参考shell编程)
      • 不带缓冲的I/O

        • 函数openreadwritelseek以及close提供了不带缓冲的I/O。这些函数都适用文件描述符。
            #include <apue.h>
            #include <error.h>
            
            #define BUFFSIZE 4096
            
            int main() {
                int n;
                char buf[BUFFSIZE];
            
                while ((n = 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);
        }
        
           #define   STDIN_FILENO   0   /* standard input file descriptor */
           #define  STDOUT_FILENO   1   /* standard output file descriptor */
           #define  STDERR_FILENO   2   /* standard error file descriptor */
        

        read函数返回读取的字节数,此值用作要写的字节数。当到达输入文件的尾端时,read返回0,程序停止执行。如果发生了一个读错误,read返回-1

      • 标准I/O

        • 标准I/O为那些不带缓冲的I/O函数提供了一个带缓冲的接口。使用标准I/O函数无需担心如何选取最佳的缓冲区大小
        • 常见的标准I/O函数:printffgets(读取一行)、getcputc
        • 标准I/O常量:stdin(标准输入)、stdout(标准输出)、EOF
            #include <apue.h>
            #include <error.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);
            }
        
    • 程序和进程
      • 程序【内核使用exec函数(7个之一)将存储在磁盘上的可执行文件(程序)读入内存,并执行】
      • 进程与进程ID
        • 进程:程序的执行实例

        • 进程ID:每个进程唯一的数字标识符,非负整数

        • 打印进程ID,getpid()函数得到当前进程ID,其返回一个pid_t类型数据,返回值保证在long数据范围内(在LLDB编译器中,看到pid_t实际上是一个int32)。

          #include <apue.h>
          int main(void){
              printf("hello world from process id %ld\n",(long)getpid());
              exit(0);
          }
          
      • 进程控制
        • 进程控制的主要函数:forkexecwaitpidexec函数有7种变体。
            #include<apue.h>
            #include<error.h>
            #include<sys/wait.h>
            
            int main(){
                char buf[MAXLINE];
                pid_t pid;
                int status;
            
                printf("%% ");
                //fgets读取一行,当键入文件终止符(一般crtl+D),返回null指针
                //fgets返回的每一行都以换行符终止,后随一个null字节,因此可用strlen计算字符串长度
                while(fgets(buf,MAXLINE,stdin)!=NULL){
                    if(buf[strlen(buf)-1]=='\n')
                        buf[strlen(buf)-1]=0;//execlp函数要求的参数是以null结尾而不是换行符
                    //fork创建一个新进程,新进程是调用进程的一个副本
                    //调用进程为父进程,新进程为子进程
                    //fork对父进程返回子进程ID,对子进程则返回0
                    //fork调用一次,返回两次(分别在父进程和子进程中)
                    if((pid=fork())<0)
                        err_sys("fork error");
                    else if(pid==0){
                        //在子进程中,调用execlp以执行从标准输入读入的命令
                        execlp(buf,buf,(char*)0);
                        err_ret("couldn't execute: %s",buf);
                        exit(127);
                    }
                    //父进程等待子进程终止
                    //waitpid指定要等待的子进程,并返回子进程的终止状态
                    if((pid=waitpid(pid,&status,0))<0)
                        err_sys("waitpid error");
                    printf("%% ");
                }
                exit(0);
        }
        
      • 线程与线程ID
        • 一个进程内部可以有多个控制线程
        • 一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。
        • 因为他们能访问同一存储区,所以各线程访问共享数据时需要采取同步措施以避免不一致性。
        • 线程ID:线程ID只在其所属的进程内起作用。
    • 出错处理
      • 文件<errno.h>中定义了errno以及可以赋予它的各种常量,这些错误常量一般以E开头
      • errno使用的两条规则:
        • 1、如果没有出错,其值不会被例程清除,所以仅在出错时检验其值
        • 2、任何函数都不会将errno设置为0.
      • 打印出错信息
        • strerror函数:将errnum(通常就是errno)映射为一个出错消息字符串,并返回
        • perror函数:基于errno值,在标准错误上产生一条出错信息,然后返回
            #include <apue.h>
            #include<errno.h>
            
            int main(int argc,char* argv[0]){
                fprintf(stderr,"EACCES: %s\n",strerror(EACCES));
                errno=ENOENT;
                perror(argv[0]);
                exit(0);
            }
        
        EACCES: Permission denied
        /Users/test/aupe3/build/1_basic/basic: No such file or directory
        
      • 出错恢复
        • 致命错误:无法执行恢复动作,最多能打印出错信息,写入日志;
        • 非致命性错误:对于资源相关的非致命错误典型的恢复操作是延时一段时间,然后重试。对于延时策略,一些应用使用指数补偿算法。
    • 用户标识
      • 用户ID:每个用户有一个唯一的用户ID,向系统标识不同的用户,用户不能更改其用户ID,root用户的用户ID为0.
      • 组ID:多个登陆项具有相同的组ID,组ID被用于将若干用户集合到项目或者部门中去,允许同组的成员之间共享资源。组文件将组名映射为数值的组ID,组文件通常是/etc/group。
      • 使用用户ID和组ID,只需4字节保存两个信息(每个以双字节整型值存放),而使用ASCII字符需要消耗更多的内存,且检验权限时,字符串比较相对比较耗时。
      • 但是对于用户而言,使用名字相比使用ID数值更加方便,所以口令文件中包含了登陆名和用户ID之间的映射关系,而组文件则包含了组名与组ID之间的映射关系。
          #include<apue.h>
      
          int main(){
              printf("uid = %d, gid = %d\n",getuid(),getgid());
              exit(0);
          }
      
    • 信号
      • 用于通知进程发生了某种情况,进程有3种处理信号的方式:
          1. 忽略信号
          1. 按系统默认方式处理
          1. 提供一个函数捕捉该信号进行处理
      • 为之前的shell进程控制例程添加信号处理函数
          #include<apue.h>
          #include<error.h>
          #include<sys/wait.h>
          
          static void sig_int(int);
          
          int main() {
              char buf[MAXLINE];
              pid_t pid;
              int status;
          
              //signal当产生了指定的信号(SIGINT)时,执行sig_int函数
              if (signal(SIGINT, sig_int) == SIG_ERR)
                  err_sys("signal error");
          
              printf("%% ");
              //fgets读取一行,当键入文件终止符(一般crtl+D),返回null指针
              //fgets返回的每一行都以换行符终止,后随一个null字节,因此可用strlen计算字符串长度
              while (fgets(buf, MAXLINE, stdin) != NULL) {
                  if (buf[strlen(buf) - 1] == '\n')
                      buf[strlen(buf) - 1] = 0;//execlp函数要求的参数是以null结尾而不是换行符
                  //fork创建一个新进程,新进程是调用进程的一个副本
                  //调用进程为父进程,新进程为子进程
                  //fork对父进程返回子进程ID,对子进程则返回0
                  //fork调用一次,返回两次(分别在父进程和子进程中)
                  if ((pid = fork()) < 0)
                      err_sys("fork error");
                  else if (pid == 0) {
                      //在子进程中,调用execlp以执行从标准输入读入的命令
                      execlp(buf, buf, (char *) 0);
                      err_ret("couldn't execute: %s", buf);
                      exit(127);
                  }
                  //父进程等待子进程终止
                  //waitpid指定要等待的子进程,并返回子进程的终止状态
                  if ((pid = waitpid(pid, &status, 0)) < 0)
                      err_sys("waitpid error");
                  printf("%% ");
              }
              exit(0);
          }
          
          void sig_int(int signo){
              printf("interrupt\n%% ");
          }
      
    • 时间值
      • 日历时间:1970.1.1 00:00:00(UTC) 这个特定时间以来所经过的秒数累计值,time_t数据结构保存这种时间类型;
      • 进程时间:CPU时间,用以度量进程使用的中央处理器资源,clock_t数据类型保存这种时间类型。
      • 度量一个进程的执行时间时,UNIX系统为一个进程维护了3个进程时间值:
        • 时钟时间;
          • 墙上时钟时间,进程运行的时间总量,其值与系统中同时运行的进程数有关;
        • 用户CPU时间;
          • 执行用户指令所用的时间量;
        • 系统CPU时间。
          • 为该进程执行内核程序所经历的时间。
    • 系统调用与库调用
      • 系统调用:各种版本的UNIX实现都提供良好定义、数量有限、直接进入内核的入口点,这些入口点称为系统调用;
      • 库调用
      • 区别:
        • 一般可以替换库函数,但是系统调用通常是不能被替换的;
        • 很多库函数会调用系统调用;
        • 系统调用通常提供一个最小接口,而库函数通常提供比较复杂的功能;
    • 习题
      • 1.4 若日历时间存放在带符号的32位整型数中,那么到哪一年溢出?怎么解决?
        • 带符号32位整型数最大值为2^{31}-1=2147483647\ s \approx 68 \ years,可知将在2038年溢出,可以换用int64位
      • 1.5 若进程时间存放在带符号的32位整型数中,而且每秒100时钟滴答,那么经过多少天后该时间溢出?
        • \frac{2^{31}-1}{100} \approx 21474836\ s \approx 248\ days
    • Reference
      • 《UNIX环境高级编程》第3版
    • 其他

    相关文章

      网友评论

          本文标题:UNIX基础知识

          本文链接:https://www.haomeiwen.com/subject/qpwdzhtx.html