02-《UNIX高级环境编程》全书精华集锦

众所周知,Linux系列知识极其重要,公司面试、实际开发都需要用到。最近看了一些资料之后,发觉自己很多地方没有掌握到位,于是开始逐一查阅,顺便整理了这个懒人版供大家参考。

1 引子

每个需要详细了解问题下面👇需要的信息都给出了相应的参考链接,有些还配上了实际操作,相信你认真看完本文之后对于UNIX操作系统基础能有一个更加清晰的认识。

2 正文

(17)在一个进程中,文件描述符的增长规律是怎样的?

例如,如果已经有0、1、2、6这样几个文件描述符,那么用open()返回的下一个文件描述符是什么?

示例图
(1)由open和openat函数返回的文件描述符一定是最小的未用描述符数值
(2)3
(3)详细链接: Linux中的文件描述符与打开文件之间的关系

##(18)什么是process id?父进程和子进程的pid之间有什么关系?

(通常子进程的pid要大于父进程的pid)

  • process id:进程标识符,unix系统中用来唯一标识每个进程,是一个非负整数。
  • 子进程总是可以调用getppid来获得父进程的pid,父进程在调用fork函数时可以获得子进程的pid.

##(19)什么是C语言程序的入口函数?在C Startup Routine(start.S)中接受的main函数原型是什么?

  • Main函数。(当内核执行C程序时,在调用main前先调用一个特殊的启动例程,可执行程序文件将此启动例程设置为程序的起始地址。启动例程从内核取得* 命令行参数和环境变量值,然后为按上述方式调用main函数做好安排。)
  • Int main(int argc, char * argv[1]),argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组。

##(20)什么是系统调用?什么是C语言库函数?它们之间有什么区别和联系?

  • linux系统调用和库函数调用的区别

  • 系统调用(system call),指运行在用户空间的应用程序向操作系统内核请求某些服务的调用过程。 系统调用提供了用户程序与操作系统之间的接口。一般来说,系统调用都在内核态执行。由于系统调用不考虑平台差异性,由内核直接提供,因而移植性较差(几乎无移植性)。

  • 库函数(library function),是由用户或组织自己开发的,具有一定功能的函数集合,一般具有较好平台移植性,通过库文件(静态库或动态库)向程序员提供功能性调用。程序员无需关心平台差异,由库来屏蔽平台差异性。

  • 系统调用实际上就是指最底层的一个调用,在linux程序设计里面就是底层调用的意思。面向的是硬件。而库函数调用则面向的是应用开发的,相当于应用程序的api.

QQ20170515-113221@2x.png
采用这样的方式有很多种原因,第一:双缓冲技术的实现。第二,可移植性。第三,底层调用本身的一些性能方面的缺陷。第四:让api也可以有了级别和专门的工作面向。

##(21)什么是inode?里面存放什么信息?文件的文件名存放在哪里?

inode(index node)就是索引节点,它用来存放档案及目录的基本信息,包含时间、档名、使用者及群组等。inode信息就存储在磁盘的某个分区上
inode 包含文件的元信息,具体来说有以下内容:

  • 文件的字节数
  • 文件拥有者的 User ID
  • 文件的 Group ID
  • 文件的读、写、执行权限
  • 文件的时间戳,共有三个:ctime 指 inode 上一次变动的时间,mtime 指文件内容上一 次变动的时间,atime 指文件上一次打开的时间。
  • 链接数,即有多少文件名指向这个 inode
  • 文件数据 block 的位置
    可以用 stat 命令,查看某个文件的 inode 信息: stat example.txt 总之,除了文件名以外的所有文件信息,都存在 inode 之中。

如下图所示,文件名存在于目录块中。
 硬盘中的一个柱面组

*应@恒大大的小迷妹°添加: Linux 中文件名存在哪儿

##(22)C程序的内存布局是怎样的?从低地址到高地址依次存放哪些段?

QQ20170515-113713@2x.png
正文段:由CPU执行的机器指令部分。
初始化数据段:程序中需明确地赋初值的变量。
非初始化数据段:在程序开始执行之前,内核将此段中的数据初始化为0或空指针。
栈:自动变量以及每次函数调用时所需保存的信息都存放在此段中。
堆:通常在堆中进行动态存储分配。
从低地址到高地址依次存放正文,初始化的数据,未初始化的数据(bss),堆,栈。
参考链接:

##(23)怎样利用fork()、exec()、waitpid()来创建和控制进程?

1.fork 函数调用一次,但返回两次。两次返回的唯一区别是:子进程返回值为 0,而父进程的返回值是新子进程的进程 ID。fork 函数返回之后,子进程和父进程都各自继续执行 fork 调用之后的指令。子进程是父进程的副本。例如,子进程获得了父进程数据空间、堆和栈的副本。
2.当进程调用一种 exec 函数时,该进程执行的程序完全替换为新程序,而新程序则从其 main 函数开始执行。调用 exec 并没有创建新进程,所以进程 ID 没有改变,exec 只是用一个新的程序替换了当前进程的正文、数据、堆和栈段。

  1. waitpid 函数通过 pid 参数来控制父进程希望获取特定进程的终止状态信息,
    • pid==-1:等待任一子进程,与 wait 函数等效。
    • pid>0:等待其进程 ID 与 pid 相等的子进程。
    • pid==0:等待其组 ID 等于调用进程组 ID 的任一子进程。(我们这里不学习进程组)
    • pid<-1:等待其组 ID 等于 pid 绝对值的任一子进程。
    waitpid 函数返回终止子进程的进程 ID。如果指定的进程或进程组不存在,或者参数 pid 指定的进程不是调用进程的子进程则都将出错。

##(24)什么是孤儿进程、什么是僵尸进程?它们有什么特点?怎样避免产生过多僵尸进程?

孤儿进程:在其父进程执行完成或被终止后仍继续运行的一类进程。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:在UNIX术语中,一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程。
1.终止进程的父进程调用wait和waitpid
2.两次调用fork()来使新创建的子进程变成孤儿进程,从而直接被1号进程收养。

##(25)什么是前台进程?什么是后台进程?一个会话有几个前台进程组和几个后台进程组?

前台进程:用户使用的有控制终端的进程。
后台进程:也称守护进程,是运行在后台的一种特殊进程。独立于控制终端并且周期性地执行某种任务或者等待处理某些发生的事件。
一个会话中包含一个前台进程组,一个或多个后台进程组。

##(26)C程序如何退出并返回操作系统?exit()函数和_exit()/_Exit()函数的差别在哪里?

进程的终止方式有 8 种,其中 5 种为正常终止,它们是

  1. 从 main 返回。
  2. 调用 exit。
  3. 调用_exit 或_Exit。
  4. 最后一个线程从其启动例程返回。
  5. 最后一个线程调用pthread_exit。
    另外三种为异常终止方式,它们是
  6. 调用 abort。
  7. 接到一个信号并终止。
  8. 最后一个线程对取消请求做出响应。
    这三个函数用于正常终止一个程序:_exit 和_Exit 立即进入内核,exit 则先执行一些清理处理(包括调用执行各终止处理程序,关闭所有标准 I/O 流等),然后进入内核。

##(27)exec函数族包含哪些具体的函数?其中execve是系统调用,其它都是普通函数。

UNIX 提供了 6 种不同的 exec 函数供我们使用。它们的原型如下所示,

#include 
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
int execvp(cosnt char *filename, char *const argv[]);

6个函数的返回值:若出错则返回-1,若成功则没有返回值
可能很多人会觉得这六个函数太难记了。但是,我们仔细观察会发现,这六个函数的命名是有一些规律的。
• 含有 l 和 v 的 exec 函数的参数表传递方式是不同的。
• 含有 e 结尾的 exec 函数会传递一个环境变量列表。
• 含有 p 结尾的 exec 函数取的是新程序的文件名作为参数,而其他exec 函数取的是新程序的路径。

##(28)什么是信号?SIGINT、SIGSTOP、SIGHUP、SIGALARM、SIGQUIT等信号是如何产生的?缺省的处理动作是什么?

在计算机科学中,信号是 Unix、类 Unix 以及其他 POSIX 兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。
SIGALRM 是闹钟信号,当由 alarm 函数设置的计时器超时后产生此信号。进程的信号屏蔽字复原为调用信号处理屏蔽字之前。
SIGQUIT,当用户在终端上按退出键(一般是Ctrl+\)时,中断驱动程序产生此信号,并发送给前台进程组中的所有进程。
SIGSTOP,作业控制信号,用以停止一个进程,当用户在终端上按挂起键(一般是Ctrl+Z)时,终端驱动程序产生此信号。
SIGHUP,如果终端接口检测到一个连接断开,则将此信号送给与该终端相关的控制进程(会话首进程)。
SIGINT,当用户按中断键(一般是Delete或Ctrl+C)时,终端驱动程序产生此信号并发送至前台进程组中的每一个进程。

##(29)什么是硬链接和软链接(符号链接)?读取软连接的函数是什么?(readlink)

硬链接:通过 i 节点链接使多个目录项指向同一个文件的这种链接类型。
符号链接:对一个文件的间接指针,一般用于将一个文件或整个目录结构移到文件系统中的另一个位置。
readlink 函数打开符号链接本身,并读取该链接中的内容(不是该链接所引用的文件的内容)。
(#include
ssize_t readlink(const char restrict pathname, char restrict buf, size_t bufsize);
返回值:若成功则返回读到的字节数,若出错则返回-1。
如果此函数成功执行,则返回读入 buf 的字节数。在 buf 中返回的符号链接的内容不以 null 字符终止。)

##(30)函数link()和unlink()的作用是什么?什么时候文件占用的磁盘空间才会真正被释放掉?(两个条件)

Link():创建一个指向现有文件的链接;unlink删除一个现有的目录项。
打开文件的进程个数为0;
文件的链接计数为0。

##(31)什么是可重入函数?怎样判断一个函数是不是可重入函数?

什么是可重入函数和不可重入函数(转) - Parry Nee - 博客园

一个可重入的函数简单来说,就是:可以被中断的函数。就是说,你可以在这个函数执行的任何时候中断他的运行,在任务调度下去执行另外一段代 码而不会出现什么错误。而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等等,所以他如果被中断的话,可能出现问题,所以这类函数是 不能运行在多任务环境下的。

基本上下面的函数是不可重入的
(1)函数体内使用了静态的数据结构;
(2)函数体内调用了malloc()或者free()函数;
(3)函数体内调用了标准I/O函数。

##(32)什么是带缓冲的输出和不带缓冲的输出?当父进程的输出缓冲区还未清空时,调用fork创建子进程,会出现什么情况?

  • 不带缓冲的输出:在用户的进程中对这这类的函数不会自动缓冲,每次执行就要进行一次系统调用,针对文件描述符操作。
  • 带缓冲的输出:在系统调用的上一层多加了一个缓冲区,针对流来操作。

如果标准输出连接到终端设备,则它是行缓冲的,否则它是全缓冲的。即以交互方式运行时,只得到父进程中printf输出的行一次,原因是标准输出缓冲区由换行符冲洗。但是当将标准输出重定向到一个文件时,却得到printf输出两次,原因是在fork之前调用了一次printf,但当调用fork时,该行数据仍在缓冲区中,然后在将父进程数据空间复制到子进程中时,该缓冲区数据也被复制到子进程中,此时父进程和子进程各自有了该行内容的缓冲区。

##(33)要求详细掌握用法的函数包括:fork、waitpid、execlp、open、read、write、lseek、close、malloc、free等。

##(34)源码:fork和printf相结合产生不同输出的源代码(forkflush.c)、用getpwnam演示的可重入函数的源代码(reentrant.c)、创建/打开/读/写文件的源代码(rwex.c等)。

3.写在最后:

欢迎大家指正批评与交流,本博客将长期更新维护