本文复习The Linux Programming Interface一书中,一些知识点的总结,源代码存在github中,在源代码中添加部分注释,用于学习与总结。

确定glibc版本

使用如下命令:

[root@centos7-10 files]# ldd /usr/bin/ls | grep libc
    libcap.so.2 => /lib64/libcap.so.2 (0x00007f7d5a0c1000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f7d59aeb000)

然后在shell窗口中执行如下命令可以看到:

[root@centos7-10 files]# /lib64/libc.so.6
GNU C Library (GNU libc) stable release version 2.17, by Roland McGrath et al.
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.8.5 20150623 (Red Hat 4.8.5-28).
Compiled on a Linux 3.10.0 system on 2018-04-10.
Available extensions:
    The C stubs add-on version 2.1.2.
    crypt add-on version 2.1 by Michael Glad and others
    GNU Libidn by Simon Josefsson
    Native POSIX Threads Library by Ulrich Drepper et al
    BIND-8.2.3-T5B
    RT using linux kernel aio
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.

可以看到libc的版本,其实我们也可以编写一个helloworld C代码,然后编译成可执行文件,使用ldd查看程序依赖库.

处理系统调用和函数库的错误

少数几个系统函数在调用时从不失败,例如getpid()总能成功返回进程的ID,而_exit()总能终止进程。无需对此类系统调用的返回值进行检查。

文件IO

所有执行I/O操作的系统调用都以文件描述符,一个非负整数(通常小整数),来指代打开的文件。文件描述符用以表示所有类型的已打开文件,包括管道(pipe)、FIFO、socket、终端、设备和普通文件。针对每个进程,文件描述符都自成一套。(Each process has its own set of file descriptors.)

改变文件偏移量

lseek函数 off_t lseek(int fd,off_t offset,int whence);
offset参数如果为SEEK_END,将文件偏移量设置为起始 与文件尾部的offset个字节,也就是说,offset参数应该从文件最后一个字节之后的下一个字节算起。
lseek()调用只是调整内核中与文件描述符相关的文件偏移量记录,并没有引起对任何物理设备的访问。

文件描述符和打开文件之间的关系

此部分涉及由内核维护的3个数据结构。
一.进程级的文件描述符表
针对每个进程内核为其维护打开文件的描述符表。该表的每一条目都记录了单个文件描述符的相关信息,例如控制文件描述符操作的日志 close-on-exec标志 对打开文件句柄的作用
二.系统级的打开文件表
内核对所有打开的文件维护有一个系统的描述表格,也称为打开文件表,存储如下信息
1.当前文件偏移量(调用read write时更新,或使用lseek直接修改)
2.打开文件时所使用的状态标志(open flags参数)
3.文件访问模式(open时只读 只写 读写模式)
4.与信号驱动I/O相关的设置
5.对该文件i-node对象的引用
三.文件系统的i-node表
1.文件类型(例如常规文件、套接字、FIFO)和访问权限
2.一个指针,指向该文件所持有的锁的列表
3.文件的各种属性,包括文件大小及与不同类型操作相关的时间戳

环境变量

printenv命令显示当前的环境列表。

cc与gcc

源代码中的Makefile使用的cc,cc与gcc到底什么关系?
在Unix中使用cc ,linux中使用gcc,可以在centos7中使用如下命令验证:

[root@centos7-10 proc]# which cc
/usr/bin/cc
[root@centos7-10 proc]# ll /usr/bin/cc
lrwxrwxrwx. 1 root root 3 Jul 25 11:35 /usr/bin/cc -> gcc
[root@centos7-10 proc]#

可以看到在linux中cc就是gcc,添加符号链接的作用是为了兼容Unix.假如我在Unix中一套源码使用cc,放在linux中可以直接运行,不用单独修改makefile中的内容。

malloc 和 free的实现

malloc的实现首先会扫描由free()所释放的空闲内存块列表以求找到尺寸大于等于所要求的一块内存,取决于具体实现,有first-fit和best-fito等扫描策略。如果存在满足尺寸的内存块,把它直接返回给调用者。如果是一块较大的内存,那么将对其进行分割,在将一块大小相当的内存返回给调用者的同时,把较小的那块空闲内存块保留在空闲列表中。
如果在空闲内存列表中找不到足够大的空闲内存块,那么malloc会调用sbrk()以分配更多的内存。为减少对sbrk()的调用次数,malloc()并未只是严格按所需字节数来分配内存,而是以更大幅度来增加program break,将超出部分置于空闲内存列表。

free()函数的实现:malloc分配内存时会额外分配几个字节来存放记录这块内存大小的整数值。该整数位于内存块的起始处,而实际返回给调用者的内存地址恰好位于这一长度记录字节之后。当将内存块置于空闲内存列表(双向链表)时,free()会使用内存块本身的空间来存放链表指针,将自身添加到列表中。

用户和组

每个用户都有一个唯一的用户名和一个与之对应的数值型用户ID。用户可以隶属于一个或多个组,每个组都有一个唯一的名称和一个与之对应的数字标识符。这些标识符的主要用途在于确立各种系统资源的所有权和访问这些资源的权限。
用户名和ID在/etc/passwd文件中加以定义,该文件也包含有关用户的其它信息。用户的属组则由/etc/passwd 和/etc/group文件中的相关字段来定义。还有一个只能由特权进程所读取的文件/etc/shadow,其作用在于将敏感的密码信息与/etc/passwd中共用的用户信息分离开来。系统还提供有不同的库函数,用于从上述各个文件中获取信息。
crypt()函数加密密码的方式与标准的login程序相同,这对需要认证用户的程序来说极为有用。

TCP/IP网络基础

数据链路层的MTU是该层所能传输的帧大小的上限。不同的数据链路层的MTU是不同的。
命令netstat -i会列出系统中的网路接口,包括其MTU如下图所示:

[root@centos7-10 tinyhttpd]# netstat -i
Kernel Interface table
Iface      MTU    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0      1500     2933      0      0 0          1544      0      0      0 BMRU
lo       65536       68      0      0 0            68      0      0      0 LRU
virbr0    1500        0      0      0 0             0      0      0      0 BMU

常量INADDR_ANY就是所谓的IPv4通配地址。通配IP地址对于将Internet domain socket绑定到多宿主机上的应用程序来讲是比较有用的。如果位于一台多宿主机上的应用程序只将socket绑定到其中一台多宿主机IP地址上,那么该socket就只能接收发送到该IP地址上的UDP数据报和TCP连接请求。将socket绑定到通配IP地址则该socket能接收任意一个主机IP地址的数据报和连接请求。