Unix环境高级编程(第二版)学习笔记
这是一次较长时间的整理,然而跳跃了一些章节和很多知识点,仍然是很不完善很不全面的。
前言 操作系统某些问题
严格意义上,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。狭义的操作系统指的是内核。
有一个Unix操作系统的体系结构如图1-1,这是广义上的操作系统,说明了内核、系统调用、库函数、shell以及应用软件之间的关系,应理解。
根据计划需要学习,第1 3 4 6 7 8 9 16章
最前面 本书源码组织结构和重要文件
以程序Program 1-1(列出一个目录中所有的文件,源码是file/ls1.c)为例,这里需要解决一个本书程序组织方式以及Linux.mk的书写问题。
文件Make.defines.linux
# Common make definitions, customized for each platform
# Definitions required in all program directories to compile and link
# C programs using gcc.
WKDIR=/root/Unix/apue/apue.2e
CC=gcc
COMPILE.c=$(CC) $(CFLAGS) $(CPPFLAGS) -c
LINK.c=$(CC) $(CFLAGS) $(CPPFLAGS) $(LDDIR) $(LDFLAGS)
LDDIR=-L../lib
LDLIBS=../lib/libapue.a $(EXTRALIBS)
CFLAGS=-DLINUX -ansi -I$(WKDIR)/include -Wall -D_GNU_SOURCE $(EXTRA)
# Our library that almost every program needs.
LIB=../libapue.a
# Common temp files to delete from each directory.
TEMPFILES=core core.* *.o temp.* *.out typescript*
文件linux.mk
include ../Make.defines.linux
PROGS = access cdpwd changemod devrdev fileflags filetype ftw4 \
hello hole longpath ls1 mycd seek testerror \
uidgid umask unlink zap
all: ${PROGS}
savedid: savedid.o
$(LINK.c) -o savedid savedid.o $(LDLIBS)
clean:
rm -f ${PROGS} ${TEMPFILES} file.hole
关于这两个文件,我还没能够完全看明白。makefile文件还没掌握!!!
第一章 Unix基础知识
Linux口令文件(/etc/passwd)——root:x:0:0:root:/root:/bin/bash——用户名:密码:用户ID:组ID:注释字段:主目录路径:默认shell
在Unix和类Unix系统中经常使用的shell有:
Bourne shell /bin/sh 最早的Shell,Bourne开发,简称bsh,或者sh
Bourne-again shell /bin/bash GNU shell兼容Bourne shell,简称bash,Linux默认shell
C shell /bin/csh BSD都支持它,简称csh。
Korn shell /bin/ksh bsh的后继,简称ksh
TENEX C shell /bin/tcsh C Shell的加强版,简称tcsh,FreeBSD和Mac OS默认shell
Solaris继承了BSD和系统V,提供上述所有的shell。
Linux下使用/etc/shells文件,存储合法shell程序路径。
root@duyuqi-OptiPlex-380:~# cat /etc/shells
# /etc/shells: valid login shells
/bin/csh
/bin/sh
/usr/bin/es
/usr/bin/ksh
/bin/ksh
/usr/bin/rc
/usr/bin/tcsh
/bin/tcsh
/usr/bin/esh
/bin/dash
/bin/bash
/bin/rbash
文件名法律:不能出现"/"(用来分割构成路径名的各文件名)和空操作符(null,用来终止路径名)
早期Unix最大文件名长度为14个字符,BSD该限制为255。现在,至少最大文件名的最大长度限制至少为255。
中,STDIN_FILENO,STDOUT_FILENO表示标准输入输出文件描述符。这是POSIX标准的一部分。
早期的Unix系统手册有8各部分集中于《Unix程序员手册》中。现在分成用户手册,程序员手册以及系统管理员手册。
第二章 Unix标准化及实现
把握三个系列的标准:ISO C,IEEE POSIX和SUS。
1989年,ANSI C标准被被批准,且被采纳为国际标准ISO/IEC 9899:1990。俗称C89
1999年,ISO C更新为ISO/IEC 9899:1999。俗称C99
POSIX.1标准包括24个ISO C头文件
POSIX(Portable Operating System Interface,可移植操作系统接口),最初版本是IEEE 1003.1-1988。
标准的目的是提高应用程序在各种Unix系统环境之间的可移植性。1003.1标准以Unix系统为基础的,但其并不限于Unix和类Unix系统。
1990年,POSIX.1标准,ISO/IEC 9945-1:1990(即IEEE Std.1003.1 1990)
1996年,ISO/IEC 9945-1:1996,包括了多线程编程接口(称为pthreads),即POSIX线程。
2001年,形成了IEEE 1003.1-2001
SUS(Single Unix Specification,单一Unix规范),是POSIX.1标准的一个超集,它定义了一些附加接口,扩展了基本POSIX.1规范所提供的功能。
相应的系统接口全集称为X/Open系统接口(XSI,X/Open System Interface),XOPEN_UNIX符号常量标识了XSI接口。只有遵循XSI实现才能称为Unix。
Open Group拥有Unix商标。
SUSv3 2001年出版,分四部分:基本定义,系统接口,Shell和实用程序以及基本理论等。2002年,被采纳为ISO/IEC 9945:2002
Unix系统和类Unix系统,前期有两个重要分支
1 AT&T分支,例如SVR4,最后一版为第十版。Solaris其后也。
2 BSD分支,4.xBSD,最终版是4.4BSD-Lite v2。FreeBSD其后也。
另外还有Linux,Mac OS X。
已通过Unix系统验证的Unix还有:AIX,HP-UX,IRIX,Unix Ware(SVR4派生的)
标准和实现尚有一定的差距,如限制和选项等,问题比较复杂和难于理解,此处暂时略过了。
第三章 文件IO
本章所讲函数被称为不带缓冲的I/O(unbuffered I/O),这与标准I/O(这是带缓冲的I/O)有区别。
文件描述符的变化范围是0~OPEN_MAX(每个进程的最大打开数目,对系统文件描述符的一种限制,它的大小依赖于系统实现)
Linux 2.4.22内核的限制是1048576,其它Unix大多认为是无穷(在软硬资源的限制内)。
重要函数Open
#include
int open(const char *pathname, int oflag, ...);成功返回文件描述符,出错返回-1。
pathname为文件路径名。
oflag为一个或多个常量的或运算构成(中定义)。三个常量必须且只能指定一个:
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开
以下常量是可选的:
O_APPEND 每次写都追加到文件尾部
O_CREAT 若此文件不存在,则创建一个文件。该选项需要第三个参数来指定文件访问权限位。
O_EXCL 同时指定O_CREAT来测试一个文件是否存在,存在出错。不存在则创建。
O_TRUNC 文件存在,且为只写或读写打开成功,将文件长度截短为0。
O_NOCTTY 若pathname为终端设备,则不将该设备分配作为此进程的控制终端。
O_NONBLOCK 若pathname指的是一个FIFO、一个快特殊文件或者一个字符特殊文件,则此选项文文件的本次打开和后续的IO操作设置非阻塞模式。
以下三个标志为SUS中,同步输入和输出选项的一部分。
O_DSYNC 使每次write等待IO完成,但如果写操作不影响读取刚写入的数据,则不必等待文件属性更新的IO。
O_RSYNC 是每一个以文件描述符作为参数的read操作等待,直至任何对文件同一部分的未决写操作都完成。
O_SYNC 使每次write都等待IO完成,包括由write引起的文件属性更新的IO
#include
int creat(const char *pathname, mode_t mode);成功为只写打开的文件描述符,出错返回-1。该函数使用于Unix的早期,现在多已不用了。
等价于open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode);
#include
int close(int fd);成功返回0,出错-1
off_t lseek(int fd, off_t offset, int whence);成功返回新的文件偏移量,出错-1。先师地为一个打开的文件设置偏移量
whence=SEEK_SET,设置文件偏移量为距文件开始处offset个字节
whence=SEEK_CUR,设置文件偏移量为当前值加上offset,offset可正可负
whence-SEEK_END,设置文件偏移量为文件长度加上offset。whence三个常量是系统V引入的。off_t为长整型
文件偏移量可以大于文件长度,此时产生文件空洞,文件空洞是允许的,空洞部分被读作0,但不需要占用磁盘空间。程序3-2有说明。
#include
ssize_t read(int fd, void *buff, size_t bytes);成功返回读到的字节数,到达文件尾端返回0.出错-1。ssize_t带符号,size_t不带符号,似乎是16位整数。
ssize_t write(int fd, const void *buff, size_t bytes);成功返回写入的字节数,出错-1。出错原因可能是磁盘写满或超过了进程的文件长度限制。
void *为通用指针,这里同ISO C保持一致。
打开文件的内核数据结构
1)每个进程在进程表中都有一个记录项。每个记录项中,有一个打开文件描述符表,打开文件描述符表中每一项表示一个打开文件描述符,其信息有:
文件描述符,文件描述符标志(例如close_on_exec),指向一个文件表项的指针。
2)内核为所有打开文件维持一张文件表。文件表项包括:
文件状态标志,当前文件偏移量,指向该文件v节点表项的指针。
3)每个代开文件都有一个v节点结构。结构中包含了文件类型,当前文件长度等信息,v节点包含i节点(索引节点)。
Linux无v节点,它使用了独立于文件系统的i节点和一个依赖于文件系统的i节点。
所以,多个进程打开同一个文件的情形,是多个文件描述符指向不同的文件表项,这多个文件表项指向同一个v节点。
#include
int dup(int fd);等价于fcntl(fd, F_DUPFD,0);
int dup2(int fd, int fd2);成功返回新的文件描述符,出错-1;等价于close(fd2);fcntl(fd, F_DUPFD, fd2);的原子操作
dup2用fd2指定新的描述符,若fd2已经打开,则现将其关闭。若fd=fd2,则dup2返回fd2而不关闭它。
新描述符(有自己的文件描述符标志)和原描述符指向同一个文件表项。dup函数总是清除新描述符的执行时关闭(close-on-exec)标志
#include
int fsync(int fd);只对fd对应的文件起作用,等待写操作完成才返回,同步更新文件的属性。可用于数据库这样的程序。
int fdatasync(int fd);类似于fsync,只影响文件的数据部分。并不是所有Unix都支持该函数。
int sync(void);将所有修改过的块缓冲区排入写队列就返回,不等待实际写操作的完成。update守护进程会周期性调用该函数,保证定期flush内核块缓冲。
Unix内核中有缓冲区高速缓存和页面高速缓存。磁盘IO采用延迟写(delayed write)的操作方式。延迟写,虽然减少了读写磁盘的次数,
但降低了文件内容的更新速度。故障发生时,可能造成文件更新内容的丢失。
以上三个函数是保证磁盘上实际文件系统与缓冲区高速缓存中内容一致性。三个函数,成功返回0,出错-1
#include
int fcntl(int files, int cmd, ...);---改变已打开的文件的性质。
fcntl函数有5种功能
1)复制一个现有的描述符(cmd=F_DUPFD)
2)获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
3)获得设置文件状态标志(cmd=F_GETFL或F_SETFL)
4)获得设置异步IO所有权(cmd=F_GETOWN或F_SETOWN)
5)获得设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)
一共10种cmd值,对这10种cmd的详细解释是:
F_DUPFD——返回值是新文件描述符。它是尚未打开的描述符中大于或等于第三个参数值(整型)的最小值。新描述符有自己的文件描述符标志,其中FD_CLOEXEC标志被清除。
同dup函数或者dup2函数的作用大体一致,但有些区别。例如,dup2是原子操作,他和fcntl有某些errno不同。
F_GETFD——返回值为文件描述符标记,只定义了一个文件描述符标记FD_CLOEXEC。系统默认是0,在exec时不关闭。1为在exec时关闭。
F_SETFD——设置fd的文件描述符标记,设置值为第三个参数(整型)
F_GETFL——返回值为文件状态标志,文件状态标志如下
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开
O_APPEND 每次写都追加到文件尾部
O_NONBLOCK 若pathname指的是一个FIFO、一个快特殊文件或者一个字符特殊文件,则此选项文文件的本次打开和后续的IO操作设置非阻塞模式。
O_DSYNC 使每次write等待IO完成,但如果写操作不影响读取刚写入的数据,则不必等待文件属性更新的IO。
O_RSYNC 是每一个以文件描述符作为参数的read操作等待,直至任何对文件同一部分的未决写操作都完成。
O_SYNC 使每次write都等待IO完成,包括由write引起的文件属性更新的IO
F_SETFL——将文件状态标志设置为第三个参数的值(整型)。可以更改的标志有,O_APPEND、O_NONBLOCK、O_DSYNC、O_RSYNC、O_SYNC
F_GETOWN——取当前接收SIGIO和SIGURG信号的进程ID或者进程组ID,信号SIGIO和SIGURG是两种异步信号。
F_SETOWN——设置接收SIGIO和SIGURG信号的进程ID或者进程组ID,正值制定一个进程ID,负值表示指定等于其绝对值的一个进程组ID
以下三种与记录锁有关,弟14.3节有讲解。记录锁的功能是,当一个进程正在读或者修改文件的某个部分时,阻止其它进程修改同一文件区。
记录锁功能中,函数为int fcntl(int fd, int cmd, struct flock *flockptr);fcntl的第三个参数是一个flock结构体:
struct flock
{
short l_type;/* F_RDLCK共享读锁,F_WRLCK独占写锁,F_UNLCK */
off_t l_start;/* 它和l_whence共同决定加锁的文件起点 */
short l_whence;/* 相对偏移量起点 */
off_t l_len;/* 加锁长度,0表示从起点到文件尾部*/
pid_t l_pid;
}
F_RDLCK共享读锁,F_WRLCK独占写锁,基本规则是多个进程在给一个给定的字节上可以有一把共享的读锁,但在一个给定字节上只能有一个进程独用的一把写锁。
读锁和写锁是排他性的。这就是读者写者思想。单一进程对同一文件区再次提出锁请求,则新锁替换老锁。
注意,加读锁时,该描述符必须是读打开;加写锁时,该描述符必须是写打开。
F_GETLK——判断flockptr所描述的锁是否会被另外一把锁所排斥。若存在则阻止创建锁,并把现存锁信息写到flockptr指向的结构中。若不存在,
则设置l_type=F_UNLCK,flockptr其它信息保持不变。——(后面两句尚不能理解)
F_SETLK——设置由flockptr所描述的锁。若因规则加锁失败。errno=EACCESS或EAGAIN
F_SETLKW——F_SETLK的阻塞版本,若当前请求期间另外进程已经有一把锁,按兼容性要求锁不能被创建,则本请求等待,进程休眠。当请求创建的锁已经可用
或者休眠由信号中断,则进程被唤醒。
第四章 文件和目录
#include
int stat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);三个函数成功返回0,出错-1
/* restrict关键字是C99新加入的关键字,char *restrict pathname可称为受限指针,
由restrict修饰的指针是唯一对指针所指向的对象(那块内存地址内容)进行存取的方法,
restrict修饰的指针主要用于函数形参,或指向由malloc()分配的内存空间。
restrict数据类型不改变程序的语义。可以指导编译器进行代码优化的目的 */
关于restrict仍然是似是而非,另外一个关键字是volatile,也没弄明白。主要是没有使用过。
stat函数返回pathname文件有关的信息结构,lstat返回符号链接有关信息,而不是符号链接应用的文件
Unix中一切皆为文件,文件信息结构如下:
struct stat
{
mode_t st_mode;//文件类型,模式字
ino_t st_ino;//i节点号
dev_t st_dev;//设备号(文件系统)
dev_t st_rdev;//特殊文件设备号,SUS XSI扩展
nlink_t st_nlink;//i节点连接计数,硬链接
uid_t st_uid;//文件所有者ID
gid_t st_gid;//文件组所有者ID
off_t st_size;//文件大小(字节)
time_t st_atime;//最后访问时间
time_t st_mtime;//最后修改时间
time_t st_ctime;//文件状态最后访问时间
time_t st_blksize;//SUS XSI扩展
time_t st_blocks;//SUS XSI扩展
}
文件类型:ls命令 测试宏;POSIX.1允许将IPC对象(如消息队列,信号量)表示为文件,但是Linux,FreeBSD,Solaris,MacOS都不将这些对象表示为文件。
1)普通文件 - S_ISREG(stat.st_mode)
2)目录文件 d S_ISDIR()
3)块文件 b S_ISCHR() 这种文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。
4)字符文件 c S_ISBLK() 提供对设备不带缓冲的访问,访问长度可变。
5)FIFO f S_ISFIFO() 命名管道(named pipe),用于IPC
6)套接字 s S_ISSOCK()
7)符号链接 l S_ISLNK()
与进程相关联的ID——这个概念好麻烦!!!
1) 实际用户ID 登录时取自口令文件/etc/passwd
2)实际组ID 登录时取自口令文件/etc/passwd
3)有效用户ID
4)有效组ID
5)附加组ID
6)保存的设置用户ID 在执行程序是包含了有效用户ID和有效组ID的副本,setuid函数
7)保存的设置组ID
通常有效用户ID等于实际用户ID,有效组ID等于实际组ID。但是当执行一个文件程序(可执行文件)时,文件模式字的两位:设置用户ID位(S_ISUID),
设置组ID位(S_ISGID)。当他们设置时,其含义是,当执行此文件时,将进程的有效用户ID(有效组ID)设置为文件所有者的用户ID(组ID)。
运行设置了S_ISUID或者S_ISGID位的可执行文件的进程通常得到额外的权限。
文件访问权限(九位)
文件所有者owner/user 文件所有者所在的组成员group 其他成员other
owner:S_IRUSR S_IWUSR S_IXUSR
group:S_IRGRP S_IWGRP S_IXGRP
other:S_IROTH S_IWOTH S_IXOTH
mode_t的位(包括以上的文件访问权限位9位,以外还有六位如下)
S_IRWXU 用户读写执行
S_IRWXG 组读写执行
S_IRWXO 其它读写执行
S_ISUID 执行时设置用户ID
S_ISGID 执行时设置组ID
S_ISVTX 保存正文(粘住位)
目录的执行权限位常被称为搜索位:要打开文件a,则包含a的目录用户都必须具有可执行权限。
目录的读权限和执行权限是不同的.
注意:
打开文件权限位(O_RDONLY,O_WRONLY,O_RDWR)
对文件指定O_TRUNC标志,文件必须具有写权限
在目录中创建文件,该目录必须具有写权限和执行权限
在目录中删除文件,该目录必须具有写权限和执行权限,文件本身不必具有读写权限。
exec调用的文件必须有可执行权限,且为普通文件。
使用open或creat创建新文件时,新文件的用户ID为进程的有效用户ID;新文件的组ID可以是进程的有效组ID,可以使他所在目录的组ID。
当open打开文件时,内核以进程的有效用户ID和有效组ID为基础执行其访问权限测试。
#include
int access(const char *pathname, int mode);--按照实际用户ID和实际组ID进行访问权限测试。成功返回0,出错-1
mode的值:
R_OK:测试读权限
W_OK:测试写权限
X_OK:测试执行权限
F_OK:测试文件是否存在
#include
mode_t umask(mode_t cmask);--设置文件模式创建屏蔽字,返回以前的值,无出错返回(这个返回值有些例外!!!)
文件模式创建屏蔽字,与每个进程相关联。cmask是9个常量的按位或。进程创建新文件或者新目录时,使用文件模式创建屏蔽字,
文件模式创建屏蔽字为1的位,文件的mode相应位关闭。系统会有一个默认的文件模式创建屏蔽字。
为了确保创建文件时指定权限位为被激活,需要进程先修改umask值,否则umask值可能会关掉该权限位。这是编程中要注意的。
进程umask值的改变不影响父进程umask,shell都有内置umask命令。
#include
int chmod(const char *pathname, mode_t mode);--更改现有文件的访问权限
int fchmod(int fd, mode_t mode);
mode_t的位(除了文件访问权限位9位,以外还有六位)
S_IRWXU 用户读写执行
S_IRWXG 组读写执行
S_IRWXO 其它读写执行
S_ISUID 执行时设置用户ID
S_ISGID 执行时设置组ID
S_ISVTX 保存正文常量(站住位)——这一位不是POSIX.1的一部分,在SUS中他被定义在XSI扩展中。
粘住位,在早期Unix中很有用。如果一可执行文件的粘住位被设置了,则在改程序第一次运行并结束时,其程序正文部分(机器指令部分)的一个
副本仍被保存在交换区,这样下次执行该程序时,能较快地将其装入内存区。交换区占用连续磁盘空间,可视为连续文件。为防止过多占用交换区,
粘住位文件数有限制。现在Unix系统配置虚拟存储系统以及快速文件系统,不需要使用这种技术。
现在,允许对目录设置粘住位,这样删除或者重命名该目录下文件的用户除了具有写权限以外还必须是(文件所有者或目录所有者或超级用户)
设置用户ID位和设置组ID位使用S表示,例如:
-rw-rwSrw-
Solaris中,只有root用户才能设置普通文件的粘住位。
/* 改变文件的用户ID和组ID */
#include
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *pathname, uid_t owner, gid_t group);成功返回0,出错-1。若owner和group任意一个是-1,则对应ID不变。
ls中文件长度是文件中实际占用的字节数(不包含文件空洞占用的磁盘块空间)。
/* 文件截短,将文件截断为length */
#include
int truncate(const char *pathname, off_t length);
int truncate(int fd, off_t length);
文件空洞,由所设置的偏移量超过文件尾端,并写了某些数据后造成的。du命令,查看文件占用的磁盘块数。
下面是个重要问题!——文件系统
Unix文件系统(Unix file system),BSD快速文件系统
磁盘0号扇区称为主引导记录(Master Boot Record,MBR),MBR的结尾是分区表(给出每个分区的起始地址和结束地址)。
表中的一个分区被标记为活动分区。计算机被引导时,BIOS读入并执行MBR,MBR确定活动分区,读入其第一个块,称为引
导块并执行。引导块中的程序将装载该分区的操作系统,保留引导块,可以保证以后可以安装操作系统。
超级块,保存文件系统重要参数。
文件系统空闲块,位图形式或指针列表
分区(引导块,超级块,文件系统空闲块,其它)
一个磁盘分成几个分区,每个分区包含一个文件系统。文件系统格式:
自举块(引导块) 超级块 柱面组0 柱面组1 ... 柱面组n
i节点,包含了大多数与文件有关的信息:文件类型,文件访问权限位,文件长度,指向该文件所占用的数据块的指针。每个i节点有
一个链接计数,其值为指向该i节点的目录项数,只有当链接计数为0时,才可以删除该文件(释放文件占用的数据块)。——这种链接称为“硬链接”
还有一种链接称为“软链接”(符号链接),实质上类似于Windows下的快捷方式。它是一种新文件,内容是所指向的文件的名字。
目录项,存放文件名和i节点编号。
每个文件系统对它们的i节点进行编号,目录项中的i节点指向同一文件系统中相应的i节点,不能使一个目录项指向另一个文件系统的i节点。
ln命令默认创建硬链接,ln -s将建立符号链接。ln命令不能跨越文件系统。
普通文件链接计数和目录文件的链接计数
mkdir test
当前目录 . 2 任何一个叶目录(不含任何子目录和文件的目录)的链接计数总是2 test/. ; test/..
上级目录 .. 3 不解释 ../. ; ../.. ; ../test
创建指向现有文件的链接(硬链接)
#include
int link(const char *path, const char *newpath);
int unlink(const char *pathname);--删除一个现有的目录项
#include
int remove(const char *pathname);--对文件unlink,对于目录与rmdir
int rename(const char *oldname, const char *newname);//文件或目录用rename函数更名。
POSIX.1 允许实现支持跨文件系统的链接,但是多实现要求六个路径名在同一文件系统中。
如果实现支持创建指向一个目录的硬链接,也仅限于root用户。理由是可能在文件系统中形成循环,而大多数处理文件系统的
程序都不能处理这种情况,很多文件系统实现不允许目录硬链接。
为了避开硬链接的限制,引入符号链接。(硬链接有文件系统限制和目录限制,符号链接可以克服这些。)
#include
int symlink(const char *actualpath, const char *sympath);actualpath不一定存在且sympath为符号链接,可以跨文件系统
ssize_t readlink(const char *restrict pathname, char *restrict buf, size_t bufsize);打开链接本身,并读符号链接中的名字。
每个文件保持有三个时间字段,表4-11中列出了函数对文件的这三个时间字段的影响。
st_atime:文件数据的最后访问时间
st_mtime:文件数据的最后修改时间
st_ctime:i节点状态的最后更改状态
i节点状态(文件的访问权限,用户ID,链接数)
#include
int utime(const char *pathname, const struct utimbuf *times);--改变一个文件的访问和修改时间
struct utimbuf
{
time_t actime;//访问时间
time_t modtime;//修改时间
}
该结构中的两个时间值是日历时间。关于时间值的问题,6.10节有介绍。
关于目录方面的操作有这些:
#include
int mkdir(const char *pathname, mode_t mode);
#include
int rmdir(const char *pathname);
int chdir(const char *pathname);
int fchdir(int filedes);
char *getcwd(char *buf, size_t size);
有权限的用户都可以写目录,但仅有内核可以写目录。
#include
DIR *opendir(const char *pathname);
struct dirent *readdir(DIR *dp);
void rewinddir(DIR *dp);
int closedir(DIR *dp);
long telldir(DIR *dp);
void seekdir(DIR *dp, long loc);
第六章 系统数据文件和信息
Unix系统地正常运行需要与系统有关的数据文件,这些文件都是ASCII码文件,Unix系统提供处理这些文件格式的接口。
口令文件/etc/passwd——访问口令文件接口 struct passwd{...}这些访问接口函数先不一一记录了。
阴影口令/etc/shadow——存放加密口令,访问阴影文件接口 struct spwd{}
组文件/etc/group——访问组文件接口 struct group{}
主机文件/etc/hosts——访问主机文件接口 struct hostent{}
网络文件/etc/networks——访问网络文件接口 struct netent{}
协议文件/etc/protocol——访问协议文件接口 struct protoent{}
服务文件/etc/services——访问服务文件接口 struct servent{}
Linux口令文件——root:x:0:0:root:/root:/bin/bash——用户名:密码:用户ID:组ID:注释字段:主目录路径:默认shell。
默认shell为/dev/null将阻止该用户登录,另外禁止用户登录的默认shell有/bin/false,/bin/true,nologin命令
nobody用户的目的是,人人都可登录到系统,但其访问权限大有限制:只能访问人人皆可读、写的文件,而nobody用户本身以及其所在用户组没有任何文件。
注释字段,携带用户信息,它不被系统实用程序解释,仅仅是注释。
Linux组文件——root:x:0:root,linuxsir——组名:组密码:组ID:用户列表(每个用户之间用','号分割;本字段可以为空;如果字段为空表示用户组为GID的用户名)
Unix的附加组
V7中,每个用户任何时候都只属于一个组,这种状态持续到1983年,4.2BSD引入了附加组。
用户可以属于口令文件中的组ID,且可以属于多达16个另外的组。附加组是POSIX.1可选项,数量限制通常是16
网络信息服务(NIS,Network Information Service)和轻量级目录访问协议(LDAP,Lightweight Directory Access Protocol)是什么???
登录账户记录使用两个数据文件:数据文件utmp记录当前登录进系统的各个用户,数据文件wtmp跟踪各个登录和注销事件
POSIX.1定义了uname函数,他返回与当前主机和操作系统有关的信息。
#include
int uname(struct utsname *name);
struct utsname
{
char sysname[];
char nodename[];
char release[];
char version[];
char machine[];
}
uname命令可以得到这些信息
Unix的时间值
Unix纪元:1970年1月1日00:00:00
国际标准时间:本初子午线(地理经度起点)格林尼治天文台原址时间,又称格林尼治时间,UTC(Coordinated Universal Time,协调世界时)。
日历时间:Unix纪元以来UTC所经过的秒数累计值。系统基本数据类型time_t
进程时间:即CPU时间,度量进程使用CPU资源,单位是时间滴答。系统基本数据类型clock_t
时钟时间:墙上时间,进程运行时间总量。它与系统中同时运行的进程数有关。
用户CPU时间:执行用户指令所用的时间
系统CPU时间:执行内核程序所经历的时间。
CPU时间:用户CPU时间和系统CPU时间之和。time -p 程序
#include
time_t time(time_t *calptr);//返回当前时间和日期
struct timeval
{
time_t tv_sev;/* seconds */
long tv_usec;/* microseconds */
}
#include
int gettimeofday(struct timeval *restrict tp,void *restrict tzp);//tzp=NULL(必须)
struct tm
{
int tm_sec;[0-60]
int tm_min;[0-59]
int tm_hour;[0-23]
int tm_mday;[1-31]
int tm_mon;[0-11]
int tm_year;//从1900年经历的年数
int tm_wday;//从星期日经历的天数
int tm_yday;//从1月1日经历的天数
int tm_isdst;//夏时制标志,夏时制生效正,非夏时制0,信息不可用负
}
#include
struct tm *gmtime(const time_t *calptr);//将日历时间转换成国际标准时间的年月日时分秒周
struct tm *localtime(const time_t *calptr);//将日历时间转换成本地时间(考虑本地时区和夏时制)
time_t mktime(struct tm *tmptr);//以本地时间的年月日等作为参数,将其转换成time_t
char *asctime(const tm *tmptr);//返回26字节字符串
char *ctime(const time_t *calptr);//返回26字节字符串
/*复杂函数类似于printf,格式化时间结果放在长度为maxsize个字符的buf数组中,返回值为buf字符数,
format参数控制时间值格式,转换说明查表*/
size_t strftime(char *restrict buf,size_t maxsize,
const char *restrict format,
const struct tm *restrict tmptr);
环境变量TZ,保存当前时区,例如:TZ=EST5EDT
夏时令(summer time;daylight saving time-DST)
以下函数变成获得进程的墙上时间和用户CPU时间和系统CPU时间
#include
clock_t times(struct tms *buf);//返回墙上时间滴答数,前后做差值,函数填写buf
struct tms
{
clock_t tms_utime;//用户CPU时间
clock_t tms_stime;//系统CPU时间
clock_t tms_cutime;//已终止的子进程用户CPU时间
clock_t tms_cstime;//已终止的子进程系统CPU时间
}
第七章 进程环境
进程终止方式8种,五种正常,三中异常:
进程的五种正常终止方式:
1)return,等效于exit,从main中返回
2)exit,其操作包括调用终止处理程序,关闭所有标准IO流
3)_Exit和_exit,为进程提供一种无需运行终止处理程序或信号处理程序而终止的方法。
4)进程的最后一个线程在其启动例程中执行返回语句。进程以终止状态0返回。
5)进程的最后一个线程调用pthread_exit函数,进程终止状态为0。
进程的三种异常终止:
1)调用abort,他产生SIGABRT信号。
2)进程接收到某些信号。
3)进程的最后一个线程对“取消”请求作出响应。
最后,执行内核代码,为进程关闭所有的打开描述符,释放存储器。
/* 正常终止 */
#include
void exit(int status);
void _Exit(int status);
#include
void _exit(int status);
上述exit函数都带有一个整型参数称为终止状态,或者称为退出状态。echo $?,可以查看上一个命令执行后的退出状态。
进程的终止状态可能是未定义的。
一个进程可以登记注册32个函数,这些函数由exit自动调用,这些函数为终止处理程序。
#include
int atexit(void (*fun)(void));//fun函数不返回值,不传参数。最终的调用顺序和登记顺序相反。
exit函数首先调用终止处理函数,清理标准IO流。若程序调用exec函数族中任一函数,则将清除所有已注册的终止处理函数。
每个程序都会接收到一张环境表,环境指针(二维指针)->环境表(一维指针)->环境字符串(例如:HOME=/home/sar)
extern char **environ;/* 全局环境变量 */ 通常使用getenv函数和putenv函数访问特定变量。
#include
char *getenv(const char *name);取得环境变量的值,ISO C没有定义任何环境变量。POSIX.1和XSI定义了一些环境变量
char *putenv(char *str);str的形式是“name=value”,将其放入环境表中。若name已经存在,先删除以前的。
int setenv(const char *name, const char *value, int rewrite);若name已存在,rewrite=0,不删除现有定义。rewrite非零,删除定义。
int unsetenv(const char *name);删除name定义。
C程序的存储空间布局,图7-3有一个典型存储器安排形象化说明。从低地址到高地址依次为:正文段,初始化数据段,非初始化数据段,堆,栈,命令行参数和环境变量。
正文段:CPU执行的机器指令部分,只读的
初始化数据段:程序中明确的赋初值的变量。
非初始化数据段:bss段,程序执行前,内核将此段中的数据初始化为0或空指针
堆:用于动态分配。由低地址到高地址分配。
栈:自动变量,函数调用所需保存的信息。由高地址到低地址分配。
size 命令可以报告正文段,数据段bss段,的长度
共享库,共享库使得可执行文件中不再需要包含公用的库例程,而只需在所有进程都可引用的存储区中维护这种库例程的一个副本。
程序第一次调用库函数,用动态链接的方法与共享库函数相链接,减少了每个可执行文件的长度,增加了一些运行时间开销。
共享库的另一个优点是可以用库函数的新版本代替老版本,而无需对使用该哭的程序重新连接编辑。
关于静态链接和动态链接在《C专家编程》中有讲解,内中还有好些问题不甚理解!!!
存储器分配——用于动态内存的分配
#include
void *malloc(size_t size);等价于realloc(NULL, size);
void *calloc(size_t nobj, size_t size);分配size*nobj的
void *realloc(void *ptr,size_t newsize);经常用于增减以前分配区的长度,newsize是存储区的新长度。
void free(void *ptr);
动态内存分配算法有best-fit,first-fit,worst-fit;quick-fit更好。
实现函数间跳转
#include
int setjmp(jmp_buf env);直接调用返回0,从longjmp调用返回非零。
void longjmp(jmp_buf env, int val);val将成为从setjmp返回的值。
jmp_buf env,通常定义为一个全局变量
每个进程都有一组资源限制,其中一些可以用一组函数查询和更改:
#include
int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);
进程限制由init 0建立,然后由后续进程继承。可以设置的资源限制取值是有限的,如表7-3所示。
struct rlimit
{
rlim_t rlim_cur;//软限制
rlim_t rlim_max;//硬限制
}
第八章 进程控制
pid=0,调度进程,交换进程,内核的一部分;
pid=1,init进程,系统进程/etc/init /sbin/init;
pid=2,页守护进程
返回进程ID,用户ID,组ID的函数:
#include
pid_t getpid(void);返回调用进程的进程ID
pid_t getppid(void);返回调用进程的父进程ID
uid_t getuid(void);返回调用进程的实际用户ID
uid_t geteuid(void);返回调用进程的有效用户ID
gid_t getgid(void);返回调用进程的实际组ID
gid_t getegid(void);返回调用进程的有效组ID
名词理解:进程的实际用户,有效用户,实际组,有效组,这些名词在第四章有讲解。
fork后,子进程是父进程的副本子进程获得父进程的数据空间,堆和栈的副本。父子进程共享正文段。
写时复制(Copy-on-Write)技术,父子进程共享数据段,堆和栈。内核将这些内存区访问权限设为只读,当父子进程试图修改这些区域
内核只为修改区域的那块内存制作一个副本。
进程的五种正常终止方式:
1)return,等效于exit
2)exit,其操作包括调用终止处理程序,关闭所有标准IO流
3)_Exit和_exit,为进程提供一种无需运行终止处理程序或信号处理程序而终止的方法。
4)进程的最后一个线程在其启动例程中执行返回语句。进程以终止状态0返回。
5)进程的最后一个线程调用pthread_exit函数,进程终止状态为0。
进程的三种异常终止:
1)调用abort,他产生SIGABRT信号。
2)进程接收到某些信号。
3)进程的最后一个线程对“取消”请求作出响应。
最后,执行内核代码,为进城关闭所有的打开描述符,释放存储器。
#include
waitpid(pid_t pid, int *stat, int options);
pid=-1 等待任一子进程,与wait等效
pid>0 等待其进程id等于pid的子进程
pid=0 等待其组id等于调用进程组id的任一子进程
pid<-1 等待其组id等于pid绝对值的任一子进程
options:此参数可以是0,对waitpid进一步控制
WCONTINUED 作业控制
WNOHANG 若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时返回值为0
WUNTRACED 作业控制
SUS的XSI扩展--waitid函数,类似于waitpid,但提供了更多的灵活性。
#include
#include
#include
#include
pid_t wait3(int *stat, int options, struct ruage *r);
pid_t wait4(pid_t pid, int *stat, int options, struct rusage *r);
wait3和wait4提供的功能比waitpid多一个——要求内核返回由终止进程及其所有子进程使用的资源汇总。
资源统计信息包括——用户CPU时间总量,系统CPU时间总量,页面出错次数,接收到信号的次数。
当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则我们人我发生了静态条件(race condition)。
其解决方法是信号机制,IPC等。
exec函数族,六个函数只有execve是系统调用,
在执行exec后,pid没变,另外很多特征都没变(如组信息,文件模式创建字,信号屏蔽,资源限制,文件锁等等)。
对打开文件的处理与每个描述符的执行时关闭(close-on-exec)标志值有关。
该标志设置,执行exec时关闭该描述符,否则该描述符仍打开。除非特定用fcntl设置该标志,否则系统默认保持描述符打开。
#include
char **environ;
int execl(const char *path,const char *arg0,...,NULL);
int execlp(const char *file,const char *arg0,...,NULL);
int execle(const char *path,const char *arg0,...,NULL,char *const envp[]);
int execv(const char *path,const char *const argv[],NULL);//argv以NULL结尾
int execvp(const char *file,const char *const argv[],NULL);
int execve(const char *path,const char *const argv[],NULL,char *const envp[]);//基础系统调用,其它函数可由他实现。
l--list,参数列表;v--vector,参数数组;p--path,$PATH变量;e--environment环境变量
更改用户ID和组ID
#include
int setuid(uid_t uid);
int setgid(gid_t gid);
第九章 进程关系
进程组,又称为作业。一个或多个进程的集合,与作业相关。pgrp,GID唯一,每个进程组有一个组长进程,
组长进程ID即PGID,组长进程可能会终止,但只要进有程组成员活跃存在,则进程组就存在。
#include
pid_t getpgrp();<=>getpgid(0);
pid_t getpgid(pid_t pid);
加入现有组或者创建新组:
pid_t setpgid(pid_t pid, pid_t pgid);
pid=pgid,设置组长进程;一个进程只能为它自己或它的子进程设置进程组ID。
会话session,一个或多个进程组集合。建立新会话:
#include
pid_t setsid();
调用者不是组长进程(是组长进程返回错误!!),则:
1)该进程成为新会话首进程。会话首进程是创建者。
2)该进程成为新进程组组长进程
3)该进程无控制终端
pid_t getsid(pid_t pid);返回 会话ID<=>会话首进程的进程组ID<=>会话首进程ID,出错 -1
子进程继承父进程的进程组ID
ps命令:
ps -o pid,ppid,pgrp,session,tpgid,comm
控制终端,
控制进程,与控制终端连接的会话首进程
一个会话的几个进程组分为一个前台进程组和一个或数个后台进程组
孤儿进程:父进程终止的进程称为孤儿进程,它由init进程收养。
孤儿进程组:该组中每个成员的父进程要么是该组的一个成员,要么是该组所属会话的成员。
第十章 信号
使用的是Linux系统
信号是软件中断,它提供了一种处理异步事件的方法。信号通常异步发生,进程预先不知道信号的准确发生时刻。
信号可以由一个进程发给另一个进程(或自身),或者由内核发给某个进程。
有两个信号不能被捕获:SIGKILL,SIGSTOP。kill –l可以列出所有信号。
$kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
Linux下信号的定义位于——中。
常用信号:
SIGABRT:夭折信号,abort()函数产生。
SIGALRM:闹钟信号,alarm()函数设置计时器超时产生。
SIGINT:中断信号,CTRL+C产生。
SIGCHLD:子进程终止向父进程发送SIGCHLD信号。
SIGKILL:用来立即结束进程的运行. 本信号不能被阻塞, 处理和忽略。杀死进程的可靠方法。
SIGSTOP:停止一个进程。
SIGTERM:要求终止进程,它可以被阻塞和处理,kill默认的处理信号。
SIGPIPE:写一个已经关闭读端的管道产生此信号,write返回-1,设置errno为EPIPE
信号处理方式:
1)忽略此信号:SIG_IGN,SIGKILL和SILSTOP不可忽略
2)捕捉信号:有信号处理程序捕捉。不能捕捉SIGKILL和SILSTOP信号
3)执行系统默认动作:SIG_DFL
关于core文件,复制该进程的存储映像,大多数Unix调试程序都使用core文件以检查进程终止时的状态。
core文件是大多数Unix系统的实现特征。不是POSIX.1的组成。
信号处理函数 void (*signal(int,void (*func)(int)))(int);
又可以写作:
typedef void Sigfunc(int);
Sigfunc *signal(int signo,Sigfunc *func);
例如:
SIGCHLD信号,当子进程终止时会向它的父进程发送SIGCHLD信号,默认行为是忽略。这样容易产生僵尸进程(Zombie/Defunct).
所谓僵尸进程就是事实上已经终止,但仍然存在的进程,它维护者子进程(僵尸进程)的pid、终止状态以及资源利用信息(CPU、内存使用量等),以便父进程在以后某个时候获取。
监视进程的处理办法是定义一个捕获SIGCHLD信号的信号处理函数 void (*signal(int,void (*func)(int)))(int);
可以写作:
typedef void Sigfunc(int);
Sigfunc *signal(int signo,Sigfunc *func);
然后父进程调用wait或者waitpid,最好是waitpid。建立一个信号处理函数并在其中调用wait并不足以防止出现僵尸进程,
Unix的信号是不排队的,解决的办法是调用waitpid(pid,stat,options);pid指定等待那个子进程,pid=-1表示等待第一个终止的子进程。
sa_mask:设置信号屏蔽字,被屏蔽的信号在信号处理函数被调用时阻塞。任何阻塞的信号不能递交给进程。POSIX保证,被捕获的信号在其信号处理函数运行期间总是阻塞的。
注意,网络编程中必须处理的三种情况:
1.当fork子进程时,必须捕获SIGCHLD信号。
2.当捕获信号时,必须处理被中断的系统调用
3.SIGCHLD信号处理函数必须正确编写,应使用waitpid函数以免留下僵尸进程。
SIGPIPE信号,Broken Pipe。当进程向某个已收到RST的套接字执行写操作时,内核向该进程发送SIGPIPE信号,默认行为是终止进程。写操作将返回EPIPE错误。
注:第一次写操作时,引发RST,第二次写操作时,引发SIGPIPE信号。写一个已接收了FIN的套接字不成问题,但是写一个已经接受了RST的套接字则是一个错误。
可靠信号和不可靠信号:
不可靠信号:信号可能对视,一个信号发生了,进程可能一直不知道,进程对信号的控制能力很差,不具备阻塞信号的能力。早期Unix信号不可靠。
可靠信号机制:不丢失信号,并且具备阻塞信号的能力。
慢系统调用:可能使进程永远阻塞的一类系统调用。如:
1)read()管道,终端设备,网络设备,数据不存在
2)write()管道,终端设备,网络设备,数据不能立即接受
3) pause(),wait()
4) ioctl
5) 某些IPC函数
对于其中的每一个信号的含义,我还没完全的弄清楚,这个麻烦而又重要的知识点,我还处于学习阶段。
第十一章 线程
线程包含了表示进程内执行环境的必须信息。其中包括进程中的线程ID,一组寄存器值,栈,调度优先级和策略,
信号屏蔽字,errno变量,线程私有数据。进程所有信息对该进程的所有线程都是共享的包括,可执行的程序文本,
程序全局内存和堆内存,栈,文件描述符。
线程接口-POSIX.1-2001,"pthread","POSIX线程"
#include
int pthread_equal(pthread_t id1, pthread_t id2);//相等返回非0;否则返回0
#include
pthread_t pthread_self(void);//获得自身线程ID,参见getpid()
#include
int pthread_create(pthread_t *restrict id,
const pthread_attr_t *restrict attr,//线程属性NULL
void *(*fun)(void *), void *restrict arg);//新线程入口,函数fun,
//如果函数参数有多个,则把这些参数放到一个结构中,然后把这个结构地址传入。
成功返回0,每个线程都提供errno的副本
C语言题外话:volatile和restrict
方便编译器优化,
restrict,C99引入,只可以用于限定指针,并表明指针是访问一个数据对象的唯一且初始的方式;
指向的内容改变,则只能通过指针进行存取。
volatile告诉编译器该变量除了可被程序修改以外还可能被其他代理修改,因此,当要求使用
volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,而不是使用寄存器中的缓存。
线程退出:
1)线程从启动历程中返回,返回值是线程退出码。
2)线程可以被统一进程中的其它线程取消。
3)pthread_exit(void *rval_ptr);
线程被取消rval_ptr指向单元置为PTHREAD_CANCELED
int pthread_join(pthread_t thread, void **rval_ptr);
//调用线程阻塞,直到指定的线程调用pthread_exit,从启动例程中返回或者被取消
自动使线程处于分离状态。
int pthread_cancel(pthread_t tid);//提出请求取消同一进程中的其他线程,不等待线程终止。
线程清理处理程序
void pthread_cleanup_push(void (*fun)(void *), void *arg);--注册清理函数fun
void pthread_cleanup_pop(int execute);--execute!=0执行清理,后注册先执行。
清理函数的调用顺序由pthread_cleanup_push函数来安排,线程执行以下动作时调用清理函数:
1)调用pthread_exit
2)响应取消请求时
3)用非零execute调用pthread_cleanup_pop时
int pthread_detach(pthread_t tid);使线程进入分离状态。
线程的状态:分离状态
线程的同步问题(互斥量,读写锁,条件变量)
互斥量
#include
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);//默认attr=NULL
int pthread_mutex_destroy(pthread_mutex_t *mutex);//成功返回0
int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁,互斥量已经上锁则线程阻塞,直到该互斥量解锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);//尝试加锁,不阻塞。已加锁,返回EBUSY
避免死锁:两个线程同时请求两一个线程拥有的资源,从而产生死锁。
产生死锁的必要条件:
1)互斥
2)占有并等待
3)非抢先
4)循环等待
读写锁(共享独占锁),允许更高的并行性(三种状态,读模式下加锁,写模式下加锁,不加锁状态)
读写锁同互斥锁一样,在使用前初始化,在释放底层内存时必须销毁。
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlock_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//读模式加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//写模式加锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//解锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);//可以获取锁,返回0;否则返回EBUSY
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
条件变量:
#include
int pthread_cond_init(pthread_cond_t *restrict cond,
pthread_cond_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict timeout);
int pthread_cond_signal(pthread_cond_t *cond);//向线程或条件发送信号
int pthread_cond_broadcast(pthread_cond_t *cond);
第十二章 线程控制
线程限制(略)
线程属性 struct pthread_attr_t{};该结构不透明
#include
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
detachstate 线程分离状态属性
guardsize 线程栈末尾的警戒缓冲区大小(字节数)
stackaddr 线程栈的最低地址
stacksize 线程栈的大小(字节数)
若对某个线程的终止状态不感兴趣,调用int pthread_detach(pthread_t id)函数,
让OS在线程退出时,回收它所占用的资源。
分离状态属性:
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr,
int *detachstate);
创建线程,以分离状态启动
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
detachstate=PTHREAD_CANCEL_DETACHE
PTHREAD_CREATE_JOINABLE(默认)
线程栈大小属性和线程栈最低地址:
int pthread_attr_getstack(const pthread_attr_t *restrict attr,
void **restrict stackaddr,
size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *attr,
void *stackaddr,
size_t *stacksize)
线程栈大小属性
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
guardsize属性,控制着线程末尾之后用以避免栈溢出的扩展内存的大小。默认PAGESIZE
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,
size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
有两个没有包含在pthread_attr_t结构中的属性——可取消状态和可取消类型,他们影响着pthread_cancel函数
调用时所呈现的行为。
可取消状态:PTHREAD_CANCEL_ENABLE默认,PTHREAD_CANCLE_DISABLE
#include
int pthread_setcancelstate(int state, int *oldstate);
void pthread_testcancel(void);//手动添加取消点。
可取消类型:PTHREAD_CANCEL_DEFERRED延迟取消,PTHREAD_CANCEL_ASYNCHRONOUS异步取消,任意时间取消
int pthread_setcanceltype(int type, int *oldtype);
同步属性
互斥量属性pthread_mutexattr_t
int pthread_mutexattr_init(pthread_mutexattr_t *mutexattr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *mutexattr);
进程共享互斥量属性:PTHREAD_PROCESS_SHARED,PTHREAD_PROCESS_PRIVATE(default)
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread-mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
类型互斥量属性:PTHREAD_MUTEX_NORMAL,PTHREAD_MUTEX_ERRORCHECK,
PTHREAD_MUTEX_RECURSIVE使之等同于信号量的功能.递归锁。
PTHREAD_MUTEX_DEFAULT,系统将其映射到其它类型
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
读写锁属性pthread_rwlockattr_t
读写锁的唯一属性是进程共享属性,参见互斥量。
条件变量属性pthread_condattr_t
也支持进程共享属性
重入问题,有了信号处理程序和线程,多个控制线程在同一时间可能潜在的调用同一个函数。
如果一个函数在同一时刻可以被多个线程安全的调用,则该函数是线程安全的。
如果一个函数对多个线程来说是可重入的,则这个函数是线程安全的,但并不说明对信号处理程序来说该函数也是可重入的。
若果函数对异步信号处理程序的重入是安全的,那么就可以说函数是异步信号安全函数。
线程安全函数(线程可重入的)和异步信号安全函数(信号处理程序可重入的)
POSIX.1不能保证是线程安全的函数,系统提供他们的线程安全版本
第十六章 网络IPC
Unix的网络编程是一个非常复杂的方面,本书的作者Stevens的另一本关于Unix网络编程的书——《Unix网络编程 卷1:套接口API》甚至比本书还要厚一倍。
还有一本叫做《Unix网络编程 卷2:进程间通信》都很麻烦。
常用网络编程Socket接口:
/*
值-结果参数(value-result),调用时进程传递内核一个值,返回时内核告诉进程一个结果
从进程到内核(socklen_t addrlen):bind、connect、sendto
从内核到进程(socklen_t *addrlen):accept、recvfrom、getsockname、getpeername
大端字节序,大端模式:高低低高,与小端模式相反(SPARC、linux为大端模式,其它有待验证)
小端字节序,小端模式:高高低低,高地址存高字节,低地址存低地址。(具体处理器使用何种模式,依据手册。一般IA体系结构为小端模式。其它有待验证)
-----注意:以上关于体系结构中的定义不确切,应当查明
以上两种由处理器、或操作系统决定的字节序称为主机字节序。
网络信息传输使用网络字节序,网际协议使用大端字节序作为网络字节序。
*/
#include
/* h-host, to, n-network, s-short,l-long */
uint16_t htons(uint16_t);
uint32_t htonl(uint32_t);
uint16_t ntohs(uint16_t);
uint32_t ntohl(uint32_t);
uint16_t--无符号16位整数,其它类比推理即可
#include
/**下面三个函数,运用在处理IPV4点分十进制串**/
int inet_aton(const char *strptr,struct in_addr *addrptr);//将strptr所指的C字符串转换成一个32位的网络字节序,并通过指针addrptr存储。成功1,失败-1。该函数没有写入正式文档
int_addr_t inet_addr(const char *strptr);//转换同上,存在问题IP 255.255.255.255不能处理,还有潜在问题。已被废弃,不述。
char *inet_ntoa(struct in_addr inaddr);//与inet_addr转换相反,返回值驻留在静态内存,他是不可重入函数
#include
/**下面两个函数,运用在处理IPV4和IPV6,p-presentation表达,n-numeric数值**/
int inet_pton(int family,const char *strptr,void *addrptr);//转换strptr字符串,addrptr存放二进制结果
const char *inet_ntop(int family,const void *addrptr,char *strptr,size_t len);//数值格式转换成表达格式,len为目标存储单元的大小
数值格式:in_addr{}、in6_addr{}
表达格式:点分十进制IPV4,128位IPV6
等价代码
inet_pton(AF_INET,str,&foo.sin_addr);<=>foo.sin_addr.s_addr = inet_addr(str);
char str[INET_ADDRSTRLEN];
ptr = inet_ntop(AF_INET,&foo.sin_addr,str,sizeof(str));<=>inet_ntoa(foo.sin_addr);
#include
/*
family:协议族(协议域/地址族),AF_XXX,eg,
AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(AF_UNIX,unix域协议)、AF_ROUTE(路由套接字)、AF_KEY(密钥套接字)
socktype:套接字类型,eg,SOCK_PACKET(linux数据链路层)
SOCK_STREAM(字节流套接字,TCP)、SOCK_DGRAM(数据报套接字,UDP)、SOCK_SEQPACKET(有序分组套接字,SCTP)、SOCK_RAW(原始套接字,IP)
protocol:协议类型常值,eg,
0(选择给定family和socktype组合的系统默认值,最常用)、IPPROTO_TCP(TCP传输协议)、IPPROTO_UDP(UDP传输协议)、IPPROTO_SCTP(SCTP传输协议)
*/
int socket(int family,int socktype,int protocol);
/*激发三路握手,一般总是又客户端发起,其异常情况
1.TCP客户端没有收到服务器对SYN分节的ACK分节,超时重传SYN分节。最终没收到ACK,则返回ETIMEDOUT错误,
2.TCP客户收到RST分节,表明服务器在对应端口上没有进程在等待,即没有服务。硬错误(hard error)返回ECONNREFUSED错误。
RST分节是TCP发生错误时发送的一种分解。其产生条件是下列条件之一:
目的地为某端口的SYN已到达,然而该端口没有正在监听的服务器;TCP想取消一个已有的连接;TCP接收到一个根本不存在的连接上的分节
3.客户端发出的SYN分节在中间路由器引发了destination unreachable的ICMP错误,软错误。返回EHOSTUNREACH或ENETUNREACH错误。
*/
int connect(int sockfd,const struct sockaddr *servaddr,socklen_t addrlen);
/*
将一个本地协议地址赋予一个套接字,当一个客户或者服务器没调用bind,当调用connect或listen时,
内核为套接字选择一个临时端口,服务器一般指定其众所周知端口,但是RPC服务器通常由内核选择一个临时端口
IPV4通配地址:INADDR_ANY
IPV6通配地址:in6addr_any INADDR_ANY_INIT
*/
int bind(int sockfd,struct sockaddr *myaddr,socklen_t addrlen);
/*
socket函数创建套接字时假设它是主动套接字;listen调用将未连接的套接字转换成被动套接字;
TCP状态:CLOSED——>LISTEN
backlog:内核应该为相应套接字排队的最大连接个数。内核为监听套接字维护两个队列,
未完成连接队列:没完成三步握手,处在SYN_RCVD状态
已完成连接队列:已经完成三步握手,处在ESTABLISHED状态
两队列之和不超过backlog,backlog还有很微妙的地方需要再研究。
*/
int listen(int sockfd,int backlog);
/*
从已完成队列头部返回下一个已完成连接,队列为空进程睡眠(假定套接字位阻塞方式)
sockfd称为监听套接字,返回值称为已连接套接字
*/
int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);
int close(int sockfd);//关闭套接字
/*sockaddr_storage地址结构能够承载系统支持的任何套接字地址结构*/
int getsockname(int sockfd,struct sockaddr *localaddr,socklen_t *addrlen);//返回与sockfd套接字关联的本地协议地址
int getpeername(int sockfd,struct sockaddr *peeraddr,socklen_t *addrlen);//返回与sockfd套接字关联的外地协议地址
小结:我对以上的章节的理解还远远不够,信号和多线程的结合,网络编程,进程间通信都是些难点。还要多多体会。