当前位置: 首页 > news >正文

UNIX基础知识:UNIX体系结构、登录、文件和目录、输入和输出、程序和进程、出错处理、用户标识、信号、时间值、系统调用和库函数

引言:

        所有的操作系统都为运行在其上的程序提供服务,比如:执行新程序、打开文件、读写文件、分配存储区、获得系统当前时间等等

1. UNIX体系结构

        从严格意义上来说,操作系统可被定义为一种软件,它控制计算机硬件资源,提供程序运行的环境。我们通常将这种软件称为内核(kernel),因为它相对较小,而且位于环境的核心。图1-1 显示了 UNIX 操作系统的体系结构。

         内核的接口被称为系统调用(system call),公用函数库构建在系统调用接口之上,应用程序可以使用公用函数库提供的接口,也可以使用内核提供的接口(系统调用)。shell 是一个特殊的应用程序,为运行其他应用程序提供了一个接口。从广义上说,操作系统包括内核和一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特性。这里所说的其他软件包括系统实用程序(system utility)、shell、公用函数库以及应用程序等。Linux 是 GUN 操作系统使用的内核,一些人将这种操作系统称为 GUN/Linux 操作系统,但是更常见的是简单地称其为 Linux。

2. 登录

2.1 用户名

        用户在登录 UNIX 系统时,输入用户名,然后输入用户密码。系统将在其口令文件(通常是 /etc/passwd 文件)中查看登录名。口令文件中的登录项由 7 个以冒号(:) 分隔的字段组成,依次是【登录名:加密口令:用户id:用户组id:注释字段:用户登录进入的起始工作目录:shell 程序路径】

在 shell 终端下输入以下命令查看 /etc/passwd 文件的内容进行验证:

cat /etc/passwd

用户名:root
加密口令:x
用户id:0
用户组id:0
注释字段:root
用户登录进入的起始工作目录:/root
用户的 shell 程序路径:/bin/bash

        加密口令:x 是一个占位符,较早期的 UNIX 系统版本中,该字段存放加密口令字。将加密口令字存放在一个人人可读的文件中是一个安全漏洞,所以现在将加密口令字存放在另一个文件中。第6章将说明这种文件以及访问它们的函数。

 2.2 shell

        用户登录后,系统通常先显示一些系统信息,然后用户就可以向 shell 程序输入命令。shell 是一个命令行解释器,它读取用户输入,然后执行命令。shell 的用户输入通常来自终端(交互式 shell),有时则来自于文件(称为 shell 脚本)。

3. 文件和目录

3.1 文件系统

        UNIX 文件系统是目录和文件的一种层次结构,所有东西的起点是根目录(root),这个目录的名称是一个字符 “/”。

        目录是一个包含目录项的文件。在逻辑上,可以认为每个目录项都包含一个文件名,同时还包含说明该文件属性的信息。文件属性是指文件类型(普通文件还是目录等)、文件大小,文件所有者、文件权限(其他用户是否有访问该文件的权限)以及文件的最后修改时间等。

3.2 文件名

        文件的名字称为文件名(filename),只有斜线(/)空字符这两个字符不能出现在文件名中。斜线用来分隔构成路径名的,空字符则用来终止一个路径名。

        创建新目录时会自动创建两个文件名:. (点)和 ..(点点) 。点指向当前目录,点点指向父目录。最高层次的根目录中,点和点点都指向当前目录。

3.3 路径名

        由斜线分隔的一个或多个文件名的序列(也可以斜线开头)构成路径名(pathname)。以斜线开头的路径名称为绝对路径(absolute pathname),否则称为相对路径(relative pathname),相对路径名指向相对于当前目录的文件。文件系统根的名字(/)是一个特殊绝对路径名,它不包含文件名。

以下代码功能:列出一个目录中所有文件的名字,ls 命令的简要实现

err.h

#ifndef ERR_H_
#define ERR_H_#include <stdarg.h>#define MAX_BUF 4096// __attribute__((noreturn)) 属性,告诉编译器这个函数永远不会有返回值,避免当函数有返回值,在某种条件下未能执行到返回值代码处,编译器警告或报错
__attribute__((noreturn)) void err_quit(const char *format, ...);
__attribute__((noreturn)) void err_sys(const char *format, ...);
void err_ret(const char *format, ...);#endif /*ERR_H_*/

err.c

#include "err.h"#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>static void err_doit(const int errno_flag, const int errorno, const char *format, va_list ap);static void err_doit(const int errno_flag, const int errorno, const char *format, va_list ap)
{char buf[MAX_BUF] = "";vsnprintf(buf, MAX_BUF-1, format, ap); // 将可变参数列表格式化到字符串中if (errno_flag)snprintf(buf+strlen(buf), MAX_BUF-strlen(buf)-1, ": %s", strerror(errorno));strcat(buf, "\n"); // 将参数字符串 "\n" 拷贝到参数 buf 所指的字符串尾。buf 要有足够的空间来容纳要拷贝的字符串。fflush(stdout); // 刷新 stdoutfputs(buf, stderr); // 将 buf 所指的字符串写入到 strerrfflush(NULL); // 参数为 NULL, fflush() 会刷新所有 stdio
}void err_quit(const char *format, ...)
{va_list ap;va_start(ap, format); // 获取可变参数列表的第一个参数的地址err_doit(0, 0, format, ap);va_end(ap); // 清空va_list可变参数列表exit(1);
}void err_sys(const char *format, ...)
{va_list ap;va_start(ap, format);err_doit(1, errno, format, ap);va_end(ap);exit(1);
}void err_ret(const char *format, ...)
{va_list ap;va_start(ap, format);err_doit(1, errno, format, ap);va_end(ap);
}

list_dir_filename.c

#include "../common/err.h"#include <dirent.h>
#include <stdio.h>int main(int argc, char *argv[])
{DIR *dirp = NULL;struct dirent *direntp = NULL;if (argc != 2)err_quit("ls dir_name");/* 打开目录 */if ((dirp = opendir(argv[1])) == NULL)err_sys("can't open %s", argv[1]);/* 逐一读取目录条目 */while ((direntp = readdir(dirp)) != NULL)printf("%s\n", direntp->d_name);/* 关闭目录 */closedir(dirp);return 0;
}

3.4 工作目录

        每个进程都有一个工作目录(working directory),有时称为当前工作目录(current working directory)。所有相对路径名都从工作目录开始解释的。进程可以用 chdir() 函数更改其工作目录。

        例如,相对路径名 unix_env_program/chapter_one/test 指的是当前工作目录中的 unix_env_program 目录中的 chapter_one 目录中的 test 文件(或目录)。从路径名可以看出 unix_env_program 和 chapter_one 是目录,但是不能分辨 test 是文件还是目录。/etc/passwd 是一个绝对路径名,它指的是根目录下的 etc 目录中的 passwd 文件(或目录)

3.5 起始目录

        登录时,工作目录设置为起始目录(home directory),起始目录从口令文件(/etc/passwd)中相应用户的登录项中获得。

4. 输入和输出

4.1 文件描述符

        文件描述符(file descriptor)通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。当内核打开一个现有的文件或创建一个新文件时,它都会返回一个文件描述符。在读写这个文件时,可以使用这个文件描述符。

4.2 标准输入、标准输出和标准错误

        每当运行一个新程序时,所有的 shell 都为其打开 3 个文件描述符,即标准输入(standard input)、标准输出(standard output)和标准错误(standard error),STDIN_FILENO(0)、 STDOUT_FILENO(1)和 STDERR_FILENO(2)为 3 个常量,定义在 #include <unistd.h> 头文件中,它们指向了标准输入、标准输出和标准出错的文件描述符。如果不做特殊处理,例如就像简单的命令 ls,则这 3 个文件描述符都链接向终端。大多数 shell 都提供一种方法,使其中任何一个或所有这 3 个文件描述符都能重新定向到某个文件,例如:

ls > file.list // 执行 ls 命令,将其标准输出重新定向到名为 file.list 的文件中。

4.3 不带缓冲的 I/O

         函数 open()、read()、write()、lseek() 以及 close() 提供了不带缓冲的 I/O,这些函数操作的都是文件描述符

以下代码功能:从标准输入中读,并向标准输出中写,用于复制任一个 UNIX 普通文件

stdin_r_stdout_w.c

#include "../common/err.h"#include <unistd.h>#define BUF_SIZE 4096int main(int argc, char *argv[])
{int nread = 0;char buf[BUF_SIZE] = "";while ((nread = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) // 从标准输入文件描述符中读{if (write(STDOUT_FILENO, buf, nread) != nread) // 写入标准输出文件描述符中err_sys("write error");}if (nread < 0)err_sys("read error");return 0;
}

将程序编译成 stdin_r_stdout_w 可执行文件,在 shell 终端输入以下命令

$./stdin_r_stdout_w > data.stdout // 标准输入是终端,标准输出重定向到 data.stdout 文件,标准出错也是终端

键盘输入:111,再换行,再输入:222,再换行,再按下文件结束符(ctrl +d),将终止本次复制。

在 shell 终端输入以下命令

$ ./stdin_r_stdout_w < data.stdout > newdata.stdout
// 标准输入重定向为 data.stdout 文件,标准输出重定向为 newdata.stdout 文件,标准出错是终端

此时,是将 data.stdout 文件复制一份,并命名为 newdata.stdout

 4.4 标准 I/O

        标准 I/O 函数为那些不带缓冲的 I/O 函数提供了一个带缓冲的接口。使用标准 I/O 函数无需担心如何选取最佳的缓冲区大小。使用标准的 I/O 函数还简化了对输入行的处理。例如,fgets() 函数读取一个完整的行,而 read() 函数读取指定的字节数。我们最熟悉的标准 I/O 函数是 printf(),在调用 printf() 函数的程序中,总是要 #include <stdio.h> ,该头文件包括了所有标准 I/O 函数的原型。

        标准 I/O 操作的对象是文件指针:FILE*

        特殊的文件指针:stdin、stdout、stderr

以下代码功能:从标准输入复制到标准输出,也是用于复制任一个 UNIX 普通文件

stdin_copy_to_stdout.c

#include "../common/err.h"#include <stdio.h>int main(int argc, char *argv[])
{int c = 0;while ((c = getc(stdin)) != EOF) // 一次读取一个字符,直到读入最后的一个字节时,返回 EOF{if (putc(c, stdout) == EOF) // 将字符写入到标准输出err_sys("output error");}if (ferror(stdin))err_sys("input error");return 0;
}

常量 EOF ,标准 I/O 常量 标准输入(stdin)和 标准输出(stdout)都在 #include <stdio.h> 头文件中定义。

5. 程序和进程

5.1 程序

        程序(program)是一个存储在磁盘上某个目录中的可执行文件。内核使用 exec() 函数,将程序读入内存,并执行程序。

5.2 进程和进程ID

        程序的执行实例被称为进程(progress)。UNIX 系统确保每个进程都有一个唯一的数字标识符,称为进程ID(progress ID)。进程ID 总是为一个非负整数。

以下代码功能:打印进程ID

print_pid.c

#include <unistd.h>
#include <stdio.h>int main(int argc, char *argv[])
{printf("pid = %ld\n", getpid());return 0;
}

5.3 进程控制

        fork()、exec() 和 waitpid() 是 3 个主要用于进程控制的主要函数。

        以下代码功能:从标准输入中读取命令,然后执行这些命令,类似于 shell 程序的基本实施部分。

execlp_demo.c

#include "../common/err.h"#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{char buf[MAX_BUF] = "";pid_t pid = 0;int status = 0;printf("%%");while ((fgets(buf, MAX_BUF, stdin)) != NULL) // 从标准输入 stdin 中读取一行{if (buf[strlen(buf) - 1] == '\n') // 最后一个是换行符时buf[strlen(buf) - 1] = 0; // 替换成 NULL,因为 execlp() 函数的参数要求字符串必须要以字符串结束符('\0')结尾if ((pid = fork()) < 0) {err_sys("fork error");} else if (pid == 0) { // 子进程/*int execlp(const char *file, const char *arg, ...);(1)execlp 函数名中 l 是 list,说明这个函数的参数是可变参数列表的意思(2)execlp 函数名中 p 是环境变量 $PATH 的意思,说明,这个函数的第一个参数可以是一个文件名,这时函数会从环境变量中去搜索这个文件的完整路径名;这个函数的第一个参数也可以是绝对路径或相对路径,这时函数不会从环境变量中去搜索这个文件的完整路径名(3)第二个参数 arg 是命令行起始地址(4) 传递给这个函数的最后一个参数必须为*/ execlp(buf, buf, (char *)NULL); // 执行读取的命令err_ret("can't exec: %s", buf); // 如果程序能跑到这里,说明执行 execlp() 函数出错了exit(127); // 子进程退出}/*pid_t waitpid(pid_t pid, int *status, int options);默认情况下 (当 options=0 时 ),waitpid挂起调用进程的执行,直到它的等待集合 (wait set) 中的一个子进程终止。如果等待集合中的一个进程在刚调用的时刻就已经 终止了,那么 waitpid 就立即返回 。在这两种情况中,waitpid返回导致 waitpid 返回的已终止子进程的PID此时,已终止的子进程已经被回收,内核会从系统中删除掉它的所有痕迹。*/// 父进程if ((pid = waitpid(pid, &status, 0)) < 0) // 子进程退出时,父进程回收子进程的资源,options=0,父进程被挂起,直到子进程退出,waitpid 立即返回err_sys("waitpid error");printf("%%");}return 0;
}

5.4 线程和线程ID 

        通常,一个进程只有一个控制线程(thread)-- 某一个时刻执行的一组机器指令。对于某些问题,如果有多个控制线程分别作用于它的不同部分,那么解决起来就容易的多。另外,多个控制线程也可以充分利用多核处理器的并行执行任务的能力。

        一个进程内的所有线程共享同一个地址空间、文件描述符、栈以及与进程相关的属性。因为它们能访问同一个存储区,所以各线程在访问共享数据时需要采取同步措施以避免数据不一致的问题。

        和进程相同,线程也有 ID 标识。但是,线程ID 只在它所属的进程内起作用。一个进程的线程ID 在另一个进程中没有意义。当在一个进程中对某个特定线程进行处理时,我们可以使用该线程的 ID 引用它。

6. 出错处理

6.1 errno 以及出错打印函数 strerror() 和 perror()

        当 UNIX 系统函数出错时,通常会返回一个负值,而且整型变量 errno 通常被设置为具有特定出错信息的值。例如,open() 函数如果成功执行则返回一个非负文件描述符,如果出错则返回 -1,errno 将会被设置,通过调用 char *str = strerror(errno); 获取到出错信息。而有些函数出错则使用另一种约定而不是返回一个负值。例如,大多数返回指向对象指针的函数,在出错时会返回一个 NULL 指针。

        头文件 <errno.h> 中定义了 errno 以及赋予它的各种常量。这些常量都以字符 E 开头。在 linux 操作系统中,出错常量在 man 3 errno 手册页查看

        POSIX 和 ISO C 将 errno 定义为一个符号,它扩展成为一个可修改的整型左值(lvalue),它可以是一个包含出错编号的整数,也可以是一个返回出错编号指针的函数。以前使用的定义是:extern int errno;

但在支持线程的环境中,多个线程共享进程地址空间,每个线程都有属于它自己的局部 errno,以避免一个线程干扰另一个线程。linux 操作系统支持多线程存取 errno,将其定义为:

extern int* __errno_location(void);

#define errno *(__errno_location())

对于 errno 应当注意两条规则。

(1)如果没有出错,其值不会被例程清除,因此,仅当函数的返回值指明出错时,才检验其值。

(2)任何函数都不会将 errno 的值设置为 0,而且在 <errno.h> 中定义的所有常量都不为 0。

C 标准定义了两个函数,用于打印出错信息

#include <string.h>
char* strerror(int errnum); // 返回值指向出错信息字符串的指针

strerror() 函数将 errnum(通常就是一个 errno 值)映射为一个出错信息字符串,并且返回此出错信息字符串的指针。

#include <stdio.h>
void perror(const char *msg);

perror 函数基于 errno 的当前值,在标准错误上参生一条出错信息,然后返回。它输出的格式为:

参数 msg 指向的字符串,然后是一个冒号,一个空格,接着是 errno 值对应的出错信息字符串,最后是一个换行符。

以下代码功能:展示 strerror() 和 perror() 函数的使用方法

strerror_perror.c

#include <stdio.h>
#include <errno.h>
#include <string.h>int main(int argc, char *argv[])
{fprintf(stderr, "EACCES: %s\n", strerror(EACCES));errno = ENOENT;perror(argv[0]);return 0;
}

 注意:我们将程序名(argv[0])作为参数传递给 perror() 函数,这是一个标准的 UNIX 惯例,使用这种方法,在程序作为管道的一部分执行时,例如:

prog1 < inputfile | prog2 | prog3 > outputfile

我们就能够分清 3 个程序中哪一个产生了一条特定的出错信息。

6.2 出错恢复

        可将在  <errno.h> 中定义的各种出错分成两类:致命性的和非致命性的。

        对于致命性的错误,无法执行恢复动作。最多能做的是在用户屏幕上打印一条出错消息或将一条出错消息写入日志文件中,然后退出。

        对于非致命性出错,有时可以较为妥善地进行处理。大多数非致命性出错是短暂的(如资源短缺),当系统中活动较少时,这种出错很可能不会发生。

        与资源相关的非致命性出错包括:

        EAGAIN 资源暂时不可用(可能与 EWOULDBLOCK 的值相同)

        ENFILE 系统中打开的文件太多

        ENOBUFS 没有可用的缓冲空间

        ENOLCK 没有可用的锁

        ENOSPC 设备上没有剩余空间

        EWOULDBLOCK 操作将阻塞(可能与 EAGAIN 的值相同)

        ENOMEM 没有足够的空间。有时也是非致命性出错。

        EBUSY 设备或资源忙。当指明共享资源正在使用时,也可将它作为非致命性的出错处理。

        EINTR 中断函数调用。当中断一个慢速系统调用时,也可将它作为非致命性出错处理。

        对于资源相关的非致命性出错的典型恢复操作是延迟一段时间,然后重试。这种技术可应用于其他情况,例如,假设出错表明一个网络连接不再起作用,那么应用程序可以采取这种方法,在短时间延迟后,尝试重新连接。一些应用使用指数补偿算法,在每次迭代中等待更长时间。

        最终,由应用的开发者决定在哪些情况下应用程序可以从出错中恢复。如果能够采用一种合理的恢复策略,那么可以避免应用程序异常终止,进而就能改善应用程序的健壮性。

7. 用户标识

7.1 用户ID

        口令文件登录项中的用户ID(user ID)是一个数值,系统用它来标识各个不同的用户。系统管理员在确定一个用户的登录名的同时,确认其用户ID。用户不能更改其用户ID。通常每个用户有一个唯一的用户ID。下面将介绍内核如何使用用户ID 来检验该用户是否有执行某些操作的权限。

        用户ID为 0 的用户为根用户(root)或超级用户(superuser)。在口令文件中,有一个登录项,其登录名为 root,我们称这种用户的特权为超级用户特权。如果一个进程具有超级用户特权,则大多数文件权限检查都不再进行。

7.2 用户的组ID

        口令文件登录项也包括用户的组ID(group ID),它是一个数值。组ID 也是由系统管理员在指定用户登录名时分配的。一般来说,在口令文件中有多个登录项具有相同的组ID。组被用于将一个或多个用户集合到项目或部门中去,这种机制允许同组的各种成员之间共享资源(如文件)。组文件将组名映射为数值的组ID。组文件通常是 /etc/group

        使用数值的用户ID 和数值的组ID 设置权限是历史上形成的。对于磁盘上的每个文件,文件系统都存储该文件所有者的用户ID 和组ID。存储这两个值只需 4 个字节(假定每个都以双字节的整形数存放)。如果使用完整 ASCII 登录名和组名,则需要更多的磁盘空间。另外,在检验权限期间,比较字符串比比较整形数更消耗时间。

        但是,对于用户而言,使用名字比使用数值方便,所以口令文件中包含了登录名和用户名ID 直接的映射关系,而组文件中包含了组名跟组ID 之间的映射关系。例如,ls -l 命令使用口令文件将数值的用户ID映射为登录名,从而打印出文件所有者的登录名。

以下程序功能:打印用户ID 和组ID

print_uid_gid.c

#include <stdio.h>
#include <unistd.h>int main(int argc, char *argv[])
{printf("uid = %d, gid = %d\n", getuid(), getgid());return 0;
}

7.3 附属组ID

        除了口令文件中对一个登录名指定一个组ID外,大多数 UNIX 系统版本还允许一个用户属于另外一些组,登录时,读文件 /etc/group,寻找列有该用户作为其成员的前 16 个记录项就可以得到该用户的附属组ID(supplementary group ID)。POSIX 要求系统至少支持 8个附属组,实际上大多数 UNIX 系统至少支持 16 个附属组。

8. 信号 

        信号(signal)用于通知进程发生某种情况。例如,某一进程执行除法操作,其除数为 0,则将名为 SIGFPE(浮点异常)的信号发送给该进程。

        进程有以下 3 种处理信号的方式。

        (1)忽略信号。有些信号表示硬件异常,例如,除以 0 或访问进程地址空间以外的存储单元等,因为这些异常产生的后果不确定,所以不推荐使用这种处理方式。

        (2)按系统默认方式处理。对于除数为 0,系统默认处理方式是终止该进程。

        (3)提供一个函数,信号发生时调用该函数,这被称为捕捉该信号,通过提供自编的处理函数,我们就能知道什么时候产生了信号,并按期望的方式处理它。

        很多情况都会产生信号。终端键盘上有两种产生信号的方法:

        (1)中断键(interrupt key,通常是 delete 键或 ctrl+c)和退出键(quit key,通常是 ctrl+\),它们被用于中断当前运行的进程。

        (2)另一种产生信号的方法是调用 kill 函数,在一个进程中调用 kill 函数就可以向另一个进程发送一个信号,当然这样做也是有限制的,当向一个进程发送信号时,我们必须是那个进程的所有者或超级用户。

以下代码功能:测试捕捉 SIGINT 信号,即捕捉按下中断键产生的信号

signal_int_demo.c

#include "../common/err.h"#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>static void sig_interrupt(int signo);int main(int argc, char *argv[])
{char buf[MAX_BUF] = "";int status = 0;pid_t pid = 0;if (signal(SIGINT, sig_interrupt) == SIG_ERR)err_sys("signal error");printf("%% ");while (fgets(buf, MAX_BUF, 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*)NULL);err_ret("can't exec: %s", buf);exit(127);}// 父进程if ((pid = waitpid(pid, &status, 0)) < 0) // 父进程回收子进程资源err_sys("waitpid error");printf("%% ");}return 0;
}void sig_interrupt(int signo)
{printf("interrupt\n%% ");
}

9. 时间值

        历史上,UNIX 系统使用过两种不同的时间值。

        (1)日历时间。该值是自协调世界时(Coordinated Universal Time,UTC),自 1970年1月1日 00:00:00 这个特定时间以来所经过的秒数累计值(早期的手册称 UTC 为格林尼治标准时间)。这些时间值可用于记录文件最近一次的修改时间等。系统基本数据类型 time_t 用于保存这种时间值。

        (2)进程时间,也被称为 CPU时间,用以度量进程使用的中央处理器资源。进程时间以时钟滴答计算,每秒钟曾经取为50、60或100个时钟滴答。系统基本数据类型 clock_t 保存这种时间值,用 sysconf 函数可以得到每秒的时钟滴答数。

        当度量一个进程的执行时间时,UNIX 系统为一个进程维护了3个进程时间值:

  • 时钟时间
  • 用户CPU时间
  • 系统CPU时间

        时钟时间又称为墙上时钟时间(wall clock time),它是进程运行的时间总量,其值与系统中同时运行的进程数有关。

        用户CPU时间是执行用户指令所用的时间量。系统CPU时间是为该进程执行内核程序所经历的时间。例如,每当一个进程执行一个系统服务时,如 read() 或 write(),在内核内执行该服务所花费的时间就计入该进程的系统CPU时间。用户CPU时间和系统CPU时间之和常被称为 CPU时间。

        要取得任一进程的时钟时间、用户CPU时间和系统CPU时间是很容易的,只要执行命令 time,其参数是要度量其执行时间的命令,例如:

$ cd /usr/include
$ time -p grep _POSIX_SOURCE */*.h > /dev/null

 10. 系统调用和库函数

        

11. 习题

(1)在系统上验证,除根目录外,目录. 和目录.. 是不同的

ls 命令的下面有两个参数:

-i 打印文件或目录的 i 节点编号。

-d 仅打印目录信息,而不是打印目录中所有文件的信息。

(2)分析以下打印进程ID的程序,说明进程ID为 852 和 853 的进程发生了什么?

 因为 UNIX 系统是多任务操作系统,在第一次执行程序打印了该程序的进程ID 后,系统中有其它两个新的进程被执行,所以占用了进程ID 为 852 和 853 的进程ID 了,我们再执行程序打印进程ID 时,此时的进程ID 则为 854

(3)perror() 函数的参数是用 const 修饰定义的,而 strerror() 函数的整型参数没有用 const 修饰定义,为什么?

        因为 perror() 函数的 msg 参数是一个指针,perror() 函数内部就可以改变这个指针指向的字符串,用 const 修饰后,说明这个指针指向的内容是常量,perror() 函数内部就不能修改这个指针指向的字符串。

        而 strerror() 函数的 errnum 参数是一个整数类型,是按值传递,因此即使 strerror() 函数内部对这个参数进行修改,对于函数外是无效的,也就没必要使用 const 属性。

(4)若日历时间存放在带符号的 32 位整型数中,那么到哪一年它会溢出?可以用什么方法扩展溢出浮点数?采用的策略是否与现有的应用相兼容?

        2^31 / 365*24*60*60 约等于 68,而日历时间是从1970年1月1日 00:00:00 开始的,所以 2038年会溢出。将 time_t 定义为 64 位整型数就可以了。如果它现在是 32 位整型数,修改为 64 位整型数后,要保证应用程序正常工作,应当对其重新编译。但是还有一个更糟糕的地方,某些文件系统及备份介质是 32 位整型数存放时间的,对于这些同样需要更新,但又需要能兼容旧的格式。

(5)若进程时间存放在带符号的 32 位整形数中,而且每秒为 100 时间滴答,那么经过多少天后,该时间会溢出?

        2^31 / 24*60*60*100 约等于 248 天

注:本文大部分内容摘抄自《UNIX环境高级编程》(第3版),少部分内容是自己的操作验证以及写的代码实例,本文只作为学习笔记

相关文章:

UNIX基础知识:UNIX体系结构、登录、文件和目录、输入和输出、程序和进程、出错处理、用户标识、信号、时间值、系统调用和库函数

引言&#xff1a; 所有的操作系统都为运行在其上的程序提供服务&#xff0c;比如&#xff1a;执行新程序、打开文件、读写文件、分配存储区、获得系统当前时间等等 1. UNIX体系结构 从严格意义上来说&#xff0c;操作系统可被定义为一种软件&#xff0c;它控制计算机硬件资源&…...

IDEA2021.3.1-优化设置IDEA2021.3.1-优化设置、快捷方式改为eclipse、快捷键等

IDEA2021.3.1-优化设置IDEA2021.3.1-优化设置、快捷方式改为eclipse、快捷键等 一、主题设置二、鼠标悬浮提示三、显示方法分隔符四、代码提示忽略大小写五、自动导包六、取消单行显示tabs七、设置字体八、配置类文档注释信息模板九、设置文件编码9.1、所有地方设置为UTF-89.2、…...

使用C#的窗体显示与隐藏动画效果方案 - 开源研究系列文章

今天继续研究C#的WinForm的显示动画效果。 上次我们实现了无边框窗体的显示动画效果(见博文&#xff1a;基于C#的无边框窗体动画效果的完美解决方案 - 开源研究系列文章 )&#xff0c;这次介绍的是未在任务栏托盘中窗体的显示隐藏动画效果的实现代码。 1、 项目目录&#xff1b…...

09_Vue3中的 toRef 和 toRefs

toRdf 作用&#xff1a;创建一个 ref 对象&#xff0c;其 value 值指向另一个对象中的某个属性。语法&#xff1a; const name toRef(person,name) 应用&#xff1a;要将响应式对象中的某个属性单独提供给外部使用时。扩展&#xff1a;toRef 与 toRefs 功能一致&#xff0…...

JAVA获取视频音频时长 文件大小 MultipartFileUtil和file转换

java 获取视频时长_java获取视频时长_似夜晓星辰的博客-CSDN博客 <dependency><groupId>ws.schild</groupId><artifactId>jave-all-deps</artifactId><version>2.5.1</version></dependency>Slf4j public class VideoTimeUtil…...

刷题笔记 day9

1658 将 x 减到 0 的最小操作数 解析&#xff1a;1. 当数组的两端的数都大于x时&#xff0c;直接返回 -1。 2. 当数组所有数之和小于 x 时 &#xff0c;直接返回 -1。 3. 数组中可以将 x 消除为0&#xff0c;那么可以从左边减小为 0 &#xff1b;可以从右边减小为 0 &#xff1…...

小白解密ChatGPT大模型训练;Meta开源生成式AI工具AudioCraft

&#x1f989; AI新闻 &#x1f680; Meta开源生成式AI工具AudioCraft&#xff0c;帮助用户创作音乐和音频 摘要&#xff1a;美国公司Meta开源了一款名为AudioCraft的生成式AI工具&#xff0c;可以通过文本提示生成音乐和音频。该工具包含三个核心组件&#xff1a;MusicGen用…...

1 swagger简单案例

1.1 加入依赖 <!--swagger图形化接口--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version> </dependency><dependency><groupId>io.spri…...

Flutter写一个android底部导航栏框架

废话不多说&#xff0c;上代码&#xff1a; import package:flutter/material.dart;void main() {runApp(MyApp()); }class MyApp extends StatelessWidget {overrideWidget build(BuildContext context) {return MaterialApp(title: Bottom Navigation Bar,theme: ThemeData(…...

关于自动化测试用例失败重试的一些思考

自动化测试用例失败重跑有助于提高自动化用例的稳定性&#xff0c;那我们来看一下&#xff0c;python和java生态里都有哪些具体做法&#xff1f; 怎么做 如果是在python生态里&#xff0c;用pytest做测试驱动&#xff0c;那么可以通过pytest的插件pytest-rerunfailures来实现…...

JS逆向之顶像滑块

本教程仅限于学术探讨&#xff0c;也没有专门针对某个网站而编写&#xff0c;禁止用于非法用途、商业活动、恶意滥用技术等&#xff0c;否则后果自负。观看则同意此约定。如有侵权&#xff0c;请告知删除&#xff0c;谢谢&#xff01; 目录 一、接口请求流程 二、C1包 三、ac 四…...

【css】textarea-通过resize:none 禁止拖动设置大小

使用 resize 属性可防止调整 textareas 的大小&#xff08;禁用右下角的“抓取器”&#xff09;&#xff1a; 没有设置resize:none 代码&#xff1a; <!DOCTYPE html> <html> <head> <style> textarea {width: 100%;height: 150px;padding: 12px 20p…...

Linux内核学习小结

网上学习总结的一些资料&#xff0c;加上个人的一些总结。 Linux内核可以分成基础层和应用层。 基础层包括数据结构&#xff0c;内核同步机制&#xff0c;内存管理&#xff0c;任务调度。 应用层包括文件系统&#xff0c;设备和驱动&#xff0c;网络&#xff0c;虚拟化等。文件…...

八、ESP32控制8x8点阵屏

引脚的说明如下 上图中 C表示column 列的意思,所有的C接高电压,即控制esp32中输出1L表示line 行的意思,所有的L接低电压,即控制esp32中输出为01. 运行效果 2. 点阵屏引脚...

使用gitee创建远程maven仓库

1. 创建一个项目作为远程仓库 2. 打包项目发布到远程仓库 id随意&#xff0c;url是打包到哪个文件夹里面 在需要打包的项目的pom中添加 <distributionManagement><repository><id>handsomehuang-maven</id><url>file:D:/workspace/java/2023/re…...

基于C#的应用程序单例唯一运行的完美解决方案 - 开源研究系列文章

今次介绍一个应用程序单例唯一运行方案的代码。 我们知道&#xff0c;有些应用程序在操作系统中需要单例唯一运行&#xff0c;因为程序多开的话会对程序运行效果有影响&#xff0c;最基本的例子就是打印机&#xff0c;只能运行一个实例。这里将笔者单例运行的代码共享出来&…...

2023-08-07力扣今日二题

链接&#xff1a; 剑指 Offer 29. 顺时针打印矩阵 题意&#xff1a; 如题 解&#xff1a; 麻烦的简单题&#xff0c;具体操作类似走地图&#xff0c;使用一个长度四的数组表示移动方向 我这边的思路是如果按正常的方向没有路走了&#xff0c;那转向下一个方向一定有路&am…...

Spring接口ApplicationRunner的作用和使用介绍

在Spring框架中&#xff0c;ApplicationRunner接口是org.springframework.boot.ApplicationRunner接口的一部分。它是Spring Boot中用于在Spring应用程序启动完成后执行特定任务的接口。ApplicationRunner的作用是在Spring应用程序完全启动后&#xff0c;执行一些初始化任务或处…...

奶牛排队 java 思维题

&#x1f468;‍&#x1f3eb; 5133. 奶牛排队 题目描述 约翰的农场有 n n n 头奶牛&#xff0c;每一头奶牛都有一个正整数编号。 不同奶牛的编号不同。 现在&#xff0c;这 n n n 头牛按某种顺序排成一队&#xff0c;每头牛都拿出一张纸条写下了其前方相邻牛的编号以及其…...

uniapp 微信小程序 判断数据返回的是jpg还是pdf,以及pdf预览

<template> <view class"approval-notice"><block v-for"(imgItem, idx) in drivingLicense" :key"idx">//如果是非图片&#xff0c;那就走pdf预览<view class"pdf-item" v-if"Object.keys(thumbnail).incl…...

SpringBoot 的事务及使用

一、事务的常识 1、事务四特性&#xff08;ACID&#xff09; A 原子性&#xff1a;事务是最小单元,不可再分隔的一个整体。C 一致性&#xff1a;事务中的方法要么同时成功,要么都不成功,要不都失败。I 隔离性&#xff1a;多个事务操作数据库中同一个记录或多个记录时,对事务进…...

Android中的ABI

Android中的ABI ABI是Application Binary Interface的缩写。 ABI常表示两个程序模块之间的接口&#xff0c;且其中一个模块常为机器码级别的library或操作系统。 ABI定义了函数库的调用、应用的二进制文件&#xff08;尤其是.so&#xff09;如何运行在相应的系统平台上等细节…...

Python爬虫在用户行为模型构建中的应用与挑战

嗨&#xff0c;大家好&#xff01;作为一名专业的爬虫代理&#xff0c;我今天要和大家分享一些关于爬虫与人类行为分析的知识。在数字化时代&#xff0c;我们每天都在互联网上留下大量的数据痕迹&#xff0c;通过分析这些数据&#xff0c;我们可以理解用户行为、性偏好和需求&a…...

LangChain与大模型的学习

这里写目录标题 问题记录1、库的版本问题 实例记录1、公司名生成2 提示模板的使用3LLM Chain 参考资料 问题记录 1、库的版本问题 openai.error.APIConnectionError: Error communicating with OpenAI: HTTPSConnectionPool(hostapi.openai.com, port443): Max retries excee…...

C语言标准定义的32个关键字

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。 …...

PE半透明屏是怎么制造的?工艺、材料、应用

PE半透明屏是一种新型的屏幕材料&#xff0c;具有半透明的特点。 它由聚乙烯&#xff08;PE&#xff09;材料制成&#xff0c;具有良好的透明度和柔韧性。PE半透明屏广泛应用于建筑、广告、展览等领域&#xff0c;具有很高的市场潜力。 PE半透明屏的特点之一是其半透明性。 它…...

linux文本三剑客---grep,sed,awk

目录 grep 什么是grep&#xff1f; grep实例演示 命令参数&#xff1a; 案例演示&#xff1a; sed 概念&#xff1a; 常用选项&#xff1a; 案例演示&#xff1a; awk 概念&#xff1a; awk常用命令选项&#xff1a; awk变量&#xff1a; 内置变量 自定义变量 a…...

leaflet-uniapp 缩放地图的同时 显示当前缩放层级

记录实现过程&#xff1a; 需求为移动端用户在使用地图时&#xff0c;缩放地图的同时&#xff0c;可以获知地图此时缩放的级别。 效果图如下&#xff1a;此时缩放地图级别为13 map.on() 有对应的诸多行为 查看官网即可&#xff0c;这里根据需要为--zoomstart zoom zoomend 代…...

[Securinets CTF Quals 2023] Admin Service,ret2libc,One is enough

只作了3个pwn&#xff0c;第4个附件没下下来&#xff0c;第5个不会 Admin Service 这是个最简单的题&#xff0c;最后来弄出来。原来只是看过关于maps文件的&#xff0c;一直没什么印象。 题目一开始设置seccomp禁用execv等&#xff0c;看来是用ORW&#xff0c;然后建了个mm…...

计算机组成原理-笔记-第二章

二、第二章——数据的表示和运算 1、进位制度&#xff08;二进制、十进制&#xff09; 2、BCD码&#xff08;余三码、2421码&#xff09; 编码方式 功能 好处 弊处 BCD码 将每个十进制数码转换为4位二进制码 精度高&#xff0c;适合直接用于数码管或LED等显示设备 编码…...