UNIX基础知识

UNIX基础知识

  • UNIX体系结构
  • 登陆
  • 文件和目录
    • 文件系统【1、目录是一个包含目录项的文件;2、根目录:所有东西的起点;】
      • 文件属性:statfstat返回包含所有文件属性的一个信息结构;
    • 文件名
      • 斜线/和空字符不能出现在文件名中,斜线用来分隔构成路径名的各文件名,空字符用来终止一个路径名
      • 两个特殊文件.(点)和..(点点),前者指向当前目录,后者指向父目录
    • 路径名【绝对路径以/开头,相对路径以.开头】
        #include
        #include
        #include 
        
        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 
          #include 
          
          #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 
          #include 
          
          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 
        int main(void){
            printf("hello world from process id %ld\n",(long)getpid());
            exit(0);
        }
        
    • 进程控制
      • 进程控制的主要函数:forkexecwaitpidexec函数有7种变体。
          #include
          #include
          #include
          
          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以及可以赋予它的各种常量,这些错误常量一般以E开头
    • errno使用的两条规则:
      • 1、如果没有出错,其值不会被例程清除,所以仅在出错时检验其值
      • 2、任何函数都不会将errno设置为0.
    • 打印出错信息
      • strerror函数:将errnum(通常就是errno)映射为一个出错消息字符串,并返回
      • perror函数:基于errno值,在标准错误上产生一条出错信息,然后返回
          #include 
          #include
          
          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
    
        int main(){
            printf("uid = %d, gid = %d\n",getuid(),getgid());
            exit(0);
        }
    
  • 信号
    • 用于通知进程发生了某种情况,进程有3种处理信号的方式:
        1. 忽略信号
        1. 按系统默认方式处理
        1. 提供一个函数捕捉该信号进行处理
    • 为之前的shell进程控制例程添加信号处理函数
        #include
        #include
        #include
        
        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位整型数最大值为,可知将在年溢出,可以换用int64位
    • 1.5 若进程时间存放在带符号的32位整型数中,而且每秒100时钟滴答,那么经过多少天后该时间溢出?
  • Reference
    • 《UNIX环境高级编程》第3版
  • 其他
    • 文中的头文件:https://www.jianshu.com/p/daebe6a07641

你可能感兴趣的:(UNIX基础知识)