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版),少部分内容是自己的操作验证以及写的代码实例,本文只作为学习笔记

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/84765.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

试卷擦除答案的工具,几个步骤轻松搞定

在学生的学习生活中&#xff0c;考试是必不可少的一部分。然而&#xff0c;有时候我们在做完试卷后发现自己填错了答案&#xff0c;或者想要更改答案&#xff0c;但是试卷上已经有了痕迹。这时候&#xff0c;我们就需要一些工具来擦除答案。以下是几个简单的步骤&#xff0c;让…

geoserver编辑样式 【开发工具QGis的初次使用】

geoserver编辑样式 开发工具配置中文语言 geoserver样式的更改 开发工具 链接: geoserver样式style的更改 链接: QGis开发工具的安装及使用 配置中文语言 setting > options > general > 中文 geoserver样式的更改 链接: geoserver样式style的更改 利用QGIs Q…

Oracle 知识篇+分区表上的索引由global改为local注意事项

★ 知识点 二、知识点 Local型索引有如下优点 1.Only one index partition must be rebuilt when a maintenance operation other than SPLIT PARTITION or ADD PARTITION is performed on an underlying table partition. 2.The duration of a partition maintenance opera…

从金蝶云星空到金蝶云星空通过接口配置打通数据

从金蝶云星空到金蝶云星空通过接口配置打通数据 源系统:金蝶云星空 金蝶K/3Cloud结合当今先进管理理论和数十万家国内客户最佳应用实践&#xff0c;面向事业部制、多地点、多工厂等运营协同与管控型企业及集团公司&#xff0c;提供一个通用的ERP服务平台。K/3Cloud支持的协同应…

设计模式行为型——访问者模式

目录 访问者模式的定义 访问者模式的实现 访问者模式角色 访问者模式类图 访问者模式举例 访问者模式代码实现 访问者模式的特点 优点 缺点 使用场景 注意事项 实际应用 访问者模式的定义 访问者模式&#xff08;Visitor Pattern&#xff09;属于行为型设计模式&am…

【工作中问题解决实践 十一】Kafka消费者消费堆积且频繁rebalance

最近有点不走运&#xff0c;老是遇到基础服务的问题&#xff0c;还是记着点儿解决方法&#xff0c;以后再遇到快速解决吧&#xff0c;今天遇到这个问题倒不算紧急&#xff0c;但也能通过这个问题熟悉一下Kafka的配置。 问题背景 正在开会的时候突然收到一连串的报警&#xff…

HarmonyOS NEXT,生命之树初长成

在不同的神话体系中&#xff0c;都有着关于生命之树的记载。 比如在北欧神话中&#xff0c;一株巨大的树木联结着九大世界&#xff0c;其被称为“尤克特拉希尔”Yggdrasill。在中国的《山海经》中&#xff0c;也有着“建木”的传说&#xff0c;它“有九欘&#xff0c;下有九枸&…

【踩坑系列记录 】Anaconda环境将torch由cpu换成gpu

概要 很早前做过深度学习&#xff0c;配环境之类的坑由于没记录都记不清了。这段时间开始做深度学习的项目&#xff0c;于是用Anaconda给项目创建了一个环境&#xff0c;其他的环境配置很顺利&#xff0c;就是到了安装pytorch时&#xff0c;我用pytorch官网的代码一直下载的是…

MongoDB安装和配置

一、MongoDB安装和配置 1、进入官网下载你所需要的安装版本&#xff0c;点击直通官网 Step1&#xff1a;进入官网后&#xff0c;将看到如下界面&#xff0c;点击上方导航栏Products&#xff0c;找到Community Server Step2&#xff1a;选择自己需要的版本、系统和压缩方式 2、下…

Kubernetes Service 工作原理

本文介绍了 Kubernetes Service 的概念、原理和具体使用。 作者&#xff1a;沈亚军 爱可生研发团队成员&#xff0c;负责公司 DMP 产品的后端开发&#xff0c;爱好太广&#xff0c;三天三夜都说不完&#xff0c;低调低调… 本文来源&#xff1a;原创投稿 爱可生开源社区出品&am…

空降流量危机?QQ音乐升级架构应对高并发

# 关注并星标腾讯云开发者 # 每周3 | 谈谈我在腾讯的架构设计经验 # 第2期 | 赵威&#xff1a;QQ音乐评论系统如何实现高可用&#xff1f; QQ 音乐自诞生以来&#xff0c;已有多个版本的评论业务系统。最新版本是19年再次全新迭代&#xff0c;基于 tlist 存储&#xff0c;按照发…

无法坚持运动?解密肠道菌群影响运动积极性

谷禾健康 运动可以说是最有效和可行的生活方式因素&#xff0c;个人可以利用它来保护自己免受各种疾病的侵害&#xff0c;包括代谢性、心血管、神经退行性和肿瘤性疾病。 世界卫生组织建议&#xff0c;每周进行150-300分钟的中等强度运动。 运动的好处具体不用多说了&#xff0…

MyBatis Plus-个人笔记

前言 学习视频 尚硅谷-Mybatis-Plus教程学习主要内容 本文章记录尚硅谷-Mybatis-Plus教程内容&#xff0c;只是作为自己学习笔记&#xff0c;如有侵扰请联系删除 一、MyBatis-Plus简介 1、简介 MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis的增强工具&#…

8.10 算法刷题【1道题】

8.10 算法刷题 22. 链表中环的入口结点&#xff08;快慢指针&#xff09; 22. 链表中环的入口结点&#xff08;快慢指针&#xff09; 原题链接 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x…

Mysql整理

一、基础概念 1. 索引 之前的文章已经写过了&#xff0c;比较细 数据库索引含义,类别,用法,创建方式_表结构加树形id和索引是为什么_马丁•路德•王的博客-CSDN博客 简单概括就是在表的某个列或者多个列或者联合表的时候加个索引&#xff0c;类似图书馆书本的索引编号&…

MachineLearningWu_15/P70-P71_AdamAndConv

x.1 算法参数更新 我们使用梯度下降算法来自动更新参数&#xff0c;但是由于学习率的不好选择性&#xff0c;我们有时候会下降地很快&#xff0c;有时候下降地很慢&#xff0c;我们期望有一种方式能够自动调整学习率的变化&#xff0c;这里引入Adaptive Moment Estimation/Ada…

LabVIEW控制通用工作台

LabVIEW控制通用工作台 用于教育目的的计算机化实验室显着增长&#xff0c;特别是用于运动控制的实验室。它们代表了各种工业应用中不断扩大的领域&#xff0c;并成为以安全的方式使用通常昂贵或独特的实验室设备进行实时实验的宝贵工具。NI LabVIEW等软件应用程序的开发和不断…

LouvainMethod分布式运行的升级之路

1、背景介绍 Louvain是大规模图谱的谱聚类算法&#xff0c;引入模块度的概念分二阶段进行聚类&#xff0c;直到收敛为止。分布式的代码可以在如下网址进行下载。 GitHub - Sotera/spark-distributed-louvain-modularity: Spark / graphX implementation of the distri…

【Echart地图】jQuery+html5基于echarts.js中国地图点击弹出下级城市地图(附完整源码下载)

文章目录 写在前面涉及知识点实现效果1、实现中国地图板块1.1创建dom元素1.2实现地图渲染1.3点击地图进入城市及返回 2、源码分享2.1 百度网盘2.2 123云盘2.3 邮箱留言 总结 写在前面 这篇文章其实我主要是之前留下的一个心结&#xff0c;依稀记得之前做了一个大屏项目的时候&…

mysql高级三:sql性能优化+索引优化+慢查询日志

内容介绍 单表索引失效案例 0、思考题&#xff1a;如果把100万数据插入MYSQL &#xff0c;如何提高插入效率 &#xff08;1&#xff09;关闭自动提交&#xff0c;只手动提交一次 &#xff08;2&#xff09;删除除主键索引外其他索引 &#xff08;3&#xff09;拼写mysql可以执…