UNIX系统函数出错时,通常会返回一个负值
错误分为致命性错误和非致命性错误
用于通知进程发生了某种状况,进程可以对信号做三种处理
忽略信号默认方式处理信号处理函数信号产生的方式多种多样,比如Ctrl+C,kill函数等等
时间分为两种,日历时间(就是时间戳)和进程时间
进程时间又分为:
时钟时间,进程运行时间总量用户CPU时间,执行用户指令所用的时间量系统CPU时间,执行内核程序所经历的时间操作系统提供多种服务的入口点,由此程序向内核请求服务
UNIX所使用的技术是为每个系统调用在标准C库中设置一个具有同样名字的函数,用户进程调用这些函数,然后函数用系统所要求的技术调用对应的内核服务
系统调用和库函数的区别
从用户角度来看,它们区别并不重要我们可以替换库函数,但是系统调用通常不能替换应用程序既可调用系统调用也可以调用库函数,很多库函数还会调用系统调用系统调用一般是最小接口,库函数提供比较复杂的功能fork exec wait通常由用户应用程序直接调用意图是提供C程序的可移植性,ISO C头文件依赖于操作系统所配置的C编译器版本
它定义了符合POSIX标准的操作系统必须提供的各种服务,它也包含了ISO C标准库函数,它的全名叫可移植的操作系统接口
ISO C标准如果和POSIX.1之间冲突,POSIX.1服从ISO C
POSIX标准分为必须部分和可选部分,必须不含如下:
POSIX接口的超集,POSIX相当于它的基本规范,只有遵循SUS标准的操作系统才能称作UNIX
ISO C,POSIX,SUS只是接口的规范,主要的实现如下
BSD 伯克利分校开发的Linux 类似于UNIX的操作系统Mac OS X 内核也是一个UNIX系统Solaris Sun公司开发的UNIX系统这四种只有Mac OS X和 Solaris 10是UNIX系统,但是他们都提供UNIX编程环境,都在不同程度上符合POSIX标准
编译时限制,在头文件中定义
运行时限制,要求进程调用一个函数获得限制值
与文件和目录无关的运行时限制(sysconf函数)与文件或目录有关的运行时限制(pathconf和fpathconf函数)如果一个特定的运行时限制啊在一个给定的系统中不改变,可以静态的定义在一个头文件中,如果没有定义在头文件中,应用程序就必须调用3个conf函数中的一个
所有编译时限制都列在头文件limits.h中
POSIX对ISO C进行了扩充
若成功,返回相应值,出错返回-1
参数是路径名
参数时文件描述符
如果我们要编写一些可移植的应用程序,而这些程序与所有得到支持的选项有关,那么就需要一种可移植到方法以决定一种实现是否支持一个给定的选项
编译时选项定义在<unistd.h>中。与文件或目录无关的选项用sysconf函数确定。与文件或目录有关的选项通过调用pathconf或fpathconf函数来发现* 如果符号常量的定义值为-1,那么该平台不支持相应的选项。
如果符号常量的定义值大于0,那么该平台支持相应的选项。如果符号常量的定义值为0,则必须调用sysconf、pathconf或fpathconf以确定相应的选项是否受到支持头文件有POSIX.1和XSI的符号,也有具体实现自己的定义,为了防止和实现冲突,我们可以通过定义功能测试宏的方式来使用POSIX.1或者XSI的定义
基本系统数据类型定义在头文件sys/types.h中,也有很多定义在其它数据类型,他们绝大多数都是_t结尾
若成功,返回文件描述符,出错返回-1
openopenat 相对于open,它可以使用相对路径打开目录中的文件返回的文件描述符一定是最小的未用描述符数值
close 关闭文件创建一个新文件,成功返回一个文件描述符,出错返回-1
可以被open函数替代,而且open函数可以以读写的方式打开文件,这样在创建临时文件时更方便。creat只能以只写的方式打开
读写操作都是从当前文件偏移量开始,打开文件除非是O_APPEND,否则是0
我们可以使用lseek给一个打开文件设置偏移量
管道,FIFO,网络套接字无法设置偏移量,我们可以用lseek来检查是否可以设置,不能设置的返回-1
设置偏移量大于文件当前长度,下次写时会形成全为0的空洞,空洞不占用磁盘存储区
read 返回值,读到的字节数,若已经到文件尾,返回0,若出错,返回-1
write 返回值,成功返回已写的字节数,若出错,返回-1,写操作从文件当前偏移量开始
在Linux中,如果两个进程各自打开了同一文件: 不同的进程的vi节点是共享的,所以多个进程操作同一个文件可能会有问题。
解决方式是将定位和写合并成一个原子操作。UNIX通过O_APPEND标志来设置原子操作。
pread 相当于lseek后调用read,但是有一定的区别pwrite 相当于lseek后调用write,同样有区别复制一个现有的文件描述符,成功返回新的文件描述符,失败返回-1
两个描述符共享相同的内部结构,共享所有的锁定,读写位置和各项权限或flags等等。例如:对一个文件描述符进行了lseek操作,另一个文件描述符的读写位置也会随之改变。不过,文件描述符之间并不共享close-on-exec flags,也就是一个关闭对另一个没有影响
UNIX系统在内核中设有缓冲区,大多数磁盘IO都通过缓冲区进行,当写数据时,先写缓冲区,然后进入队列,最后进入磁盘
sync 将缓冲区写入队列后返回fsync 只对特定文件描述符起作用,磁盘操作结束返回,确保修改过的块立即写到磁盘fdatasync 它只影响文件数据部分,而fsync还会同步更新文件属性I/O操作杂物箱,很多IO操作都能用ioctl表示,主要用于终端I/O
重点是stat函数,它通过文件名filename获取文件信息,并保存在buf所指的结构体stat中
int stat(const char *file_name, struct stat *buf);下面是buf的结构体:
struct stat { dev_t st_dev; //文件的设备编号 ino_t st_ino; //节点 mode_t st_mode; //文件的类型和存取的权限 nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1 uid_t st_uid; //用户ID gid_t st_gid; //组ID dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号 off_t st_size; //文件字节数(文件大小) unsigned long st_blksize; //块大小(文件系统的I/O 缓冲区大小) unsigned long st_blocks; //块数 time_t st_atime; //最后一次访问时间 time_t st_mtime; //最后一次修改时间 time_t st_ctime; //最后一次改变时间(指属性) };用户(读,写,执行) 组(读,写,执行) 其他(读,写,执行)
打开一个文件,需要对上级目录有执行权限,对文件本身有该有的权限 目录的执行权限位常被称为搜索位目录的读权限和执行权限是由区别的在目录中创建一个新文件,需要对目录有写权限和执行权限在目录中删除文件,需简要对目录有写权限和执行权限,对文件不需要权限与一个进程关联的ID至少有6个:
实际 实际用户ID实际组ID 权限 有效用户ID,通常为实际用户ID有效组ID,通常为实际组ID附属组ID exec函数保存 保存的设置用户ID保存的设置组ID实际用户是用来标识进程是谁,是谁在执行进程,一般是登录用户;而有效用户ID则标识这该进程的访问权限在大多数情况下,实际ID和有效ID一般情况下是一样的,但是在有些情况就是不一样的了,具体可见:
Linux特殊权限位
当我们创建新文件时:
新文件用户ID为进程有效有效用户ID组ID POSIX允许有下面两种方式任意一种设置,Linux默认是目录组ID,目录组ID没有则是进程组ID 进程有效组ID所在目录组ID用实际用户ID和实际组ID进行访问权限测试
当我们登录系统之后创建一个文件总是有一个默认权限的,那么这个权限是怎么来的呢?这就是umask干的事情。umask设置了用户创建文件的默认权限,它与chmod的效果刚好相反,umask设置的是权限“补码”
修改文件权限,进程的有效用户ID必须等于文件所有者ID,或者该进程必须具有超级用户权限
主要用于更改文件的用户ID和组ID
st_size表示以字节为单位的文件长度,只针对普通文件,目录文件和符号链接有意义
EOF end-of-file表示文件结束
st_blksize 文件的块大小,文件系统给文件分配空间时的最小单位,例如512字节,这个不固定 st_blocks 文件包含的块数,文件全部内容应该根据st_size读取,而非块大小和块数相乘
文件中的空洞: lseek函数显示地为一个打开文件设置偏移量,文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被读为0,空洞是否占用硬盘空间是由文件系统(file system)决定的
磁盘可以分成一个或多个分区,每个分区可以包含一个文件系统
i节点就是inode,指向磁盘上该文件存储区,i节点不包含文件名目录项中包括文件名和i节点多个文件名指向同一索引节点(Inode)就称为硬链接
硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬连接文件均被删除。
硬链接的限制:
连接和文件需要位于同一文件系统只有超级用户才能创建指向目录的硬链接软链接文件有类似于Windows的快捷方式。它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。
假设符号链接没了,不会影响到原始数据。假设原始数据没了,那我这个符号链接就变成了一张空头支票,也就是悬空的符号链接
软连接可以跨文件系统,可以对目录使用
每个文件系统都构建在存储设备上,位置由主,次设备号确定
主设备号标识设备驱动程序
次设备号标识特定的子设备
比如同一个磁盘驱动器上各文件系统具有相同的主设备号,但是次设备号不同
fopen/freopen/fdopen 打开流,若成功,返回文件指针,出错返回NULL
fclose 关闭流,若成功,返回0,若出错,返回EOF
每次一个字符读写,如果流式带缓冲的,则标准I/O函数处理所有缓冲
输入函数 getc fgetc getchar输出函数 putc fputc putchar每次一行读写
输入一行fgets/gets,gets(已弃用)从标准输入读,fgets从指定的流读输出一行fputs/puts,puts(已弃用)写到标准输出,fputs写到指定的流直接IO,fread/fwrite
如果进行二进制I/O操作,我们更愿意一次读或写一个完整的结构,因为有了下面两个函数
fread 读取fwrite 写入二进制IO在跨系统时要有互认的规范模式,也就是网络协议交换二级制数据的某些技术
标准I/O库最终都调用I/O例程,每个标准I/O流都有一个与其相关的文件描述符,我们可以使用fileno方法来获取描述符
内存流只访问主存,不访问磁盘上的文件,所以性能更高
标准IO效率不高,每当使用fgets的时候,需要复制两次数据,一次是内核和标准IO缓冲区,第二次是标准IO缓冲区到用户程序的行缓冲区,后来有了fio,sfio,ASI等使性能大大的提升
加密口令存放位置/etc/passwd,里面大多数有7个字段,字段之间用冒号分割
root:*:0:0:System Administrator:/var/root:/bin/sh我们可以看到,加密口令字段用*来代替
为了防止用户暴力破解加密口令,很多系统将加密口令存放到一个叫阴影口令/etc/shadow的文件中,里面存放了用户登录名和加密口令
阴影口令文件不是一般用户可以读取的
组文件就是维护组用户信息的地方
在很早以前,一个用户只能属于一个组,后来有了附属组ID的概念,一个用户不仅可以属于口令文件中组ID对应的组,还可以属于至多16个另外的组,这时候对文件访问权限检查时,文件组ID不仅比较进程有效组ID,还会和附属组ID比较
使用附属组ID的优点是不需要经常更改组
一般情况下,每个数据文件都包含三个函数:
get 读set 写end 关闭需要存放在磁盘程序文件中的段只有正文段和初始化数据段
共享库使得可执行文件中不再需要包含共用的库函数,而只需在所有进程都可引用的存储区保存这种库例程的一份副本,程序第一次执行或者第一次调用某个库函数时,用动态链接方法将程序与共享库函数相链接
这里修改不会影响父进程,只会影响自己和子进程
getenv 取其环境变量值putenv 以前有,就会删除以前的setenv 根据rewrite来判断是否删除以前的unsetenv 删除创建的新进程为子进程,fork函数被调用后返回两次,父进程返回新建子进程ID,子进程返回ID0,这个0可不是上面所说的系统进程,子进程获得父进程数据空间,堆,栈的副本,但是他们并不共享这些存储空间部分,他们共享正文段
这里有引入了一个写时复制的概念,也就是说,fork子进程先和父进程共享存储空间,写的时候再真正做副本,有的时候父进程fork时也会将缓冲区内容复制到子进程
后来有了clone函数,可以控制共享范围
fork的一个特征是父进程的所有打开文件描述符都被赋值到子进程中,父进程和子进程每个相同的打开描述符共享一个文件表项
父进程和子进程共享同一个文件偏移量
fork的常见用法:
父进程接受网络请求,子进程处理网络请求vfork函数用于创建一个新进程,而新进程的目的是exec一个新程序,它可以保证子进程先运行
当一个进程终止时,内核会向父进程发送信号,这种信号的系统默认动作是忽略它
当父进程调用wait时:
wait 阻塞到有一个子进程终止。没有子进程的会出错waitpid 可以指定进程号,也可以选择不阻塞waitid 可以指定进程组号的所有子进程wait3/wait4 可以返回由终止进程机器所有子进程使用的资源概况在执行exec函数时,如果filename包含/,则视其为路径名,否则就按PATH环境变量处理,在它所指定的各目录中搜寻可执行文件,PATH变量用:分割
大部分UNIX实现中,只有execve是内核系统的调用,另外6个只是库函数,他们最终都是调用execve函数
在设计应用时,一般试图使用最小特权模型
shell脚本就是一个解释器文件
exec函数执行的是改解释器文件中第一行pathname的文件
/var/account/paccount文件中会记录每个结束进程的信息,在进程结束时会按终止顺序会进行记录
acct函数用启动和禁用进程会计,会计记录写到指定的文件中:
在mac系统中为 /var/account/acct在linux系统中为 /var/account/pacct会计进程对应的是进程,而不是程序,如果一个进程执行三个程序,只会生成一个会计记录
time函数可以获得它自己以及终止子进程的:
墙上时钟时间用户CPU时间系统CPU时间UNIX目前已经支持多种身份的方式,比如PAM,允许管理人员来灵活的配置登录权限,这里只说传统的终端登录方式:
在/etc/ttys或者/etc/inittab中,每个终端设备都有一行系统自举时,内核创建进程ID为1的init进程init读取文件/etc/ttys或者/etc/inittab,对每一个终端设备调用一次forkfork出来的子进程执行getty程序输出login之类的信息等待用户键入,之后调用登录程序,然后输入密码登录陈宫,将工作目录更改为该用户的起始目录等系统使用伪终端的软件驱动程序,使他同一个软件既能处理终端登录,又能处理网络登录
在系统中,有一个inetd/xinetd进程来等待大多数网络连接,它等待TCP/IP连接请求,当一个连接请求到达时,它执行一次fork,然后生成的子进程来exec适当的程序
我们以telnet为例: 在上图的过程之后,telnetd进程打开一个伪终端设备,然后分成两个进程,一个处理网络连接,一个执行login程序,它们通过伪终端相连
通常,多个进程组合起来可以形成一个一起干活的进程组,每个进程组有个组长进程,组长进程的进程ID等于进程组ID
进程组的目的是为了完成一个任务,同时管理多个进程的启动终止等,进程有且只有一个进程组
只要某个进程组有一个进程存在,该进程组就存在,和组长进程无关,进程也可以转移到另外的进程组
会话是一个或多个进程组的集合,当一个用户登录一次系统就形成一次会话,一个会话可包含多个进程组,但只有一个前台进程组,进程组组长进程不能创建新的会话
Shell调用本质其实是由shell(该系统的shell类型为bash)调用fork()函数创建一个进程,由该创建的子进程去调用一种exec函数去执行另一个程序。当子进程调用一种exec函数时,该子进程的执行的程序完全替换为新进程,因为调用exec函数并不创建新进程,所有前后进程的进程ID并不改变
该进程组的每个成员的父进程要么是该组的成员,要么在其它会话中 进程组1不是孤儿进程组,进程组2是孤儿进程组
守护进程没有控制终端,大部分守护进程都是以超级用户特权运行
常见的守护进程
rpcbind 将远程过程调用程序号映射为网络端口号cron 定时任务处理出错消息: syslog来处理出错的消息
守护进程的惯例:
锁文件存放在/var/run下面配置文件在/etc/xxx.conf,而且修改配置文件对守护进程不一定生效守护进程可以在/etc/inittab配置重新启动守护进程设置如何处理信号
在不同的实现有不同的语义,推荐使用sigaction函数
当执行一个程序时,所有信号的状态都是系统默认或忽略进程fork时,子进程进程父进程的信号处理方式早期UNIX系统如果一个进程在执行一个低速系统调用而阻塞期间收到一个信号,则系调用不再继续执行,返回出错,这个就唤醒了一个阻塞的系统调用
低速系统调用是指可能会使进程永远阻塞的一类系统调用,比如读一个数据不存在管道等等
注意:与磁盘IO相关的系统调用,只会暂时阻塞调用者,除非发生硬件错误,一般会很快返回
后来在Liunx中,当信号处理程序时signal函数时,被中断的系统调用会重启动,在sigaction中,是可选的
当一个进程正在执行malloc分配存储空间,而此时捕获信号又执行malloc,那么很可能对进程造成破坏。
在unix中,有很多可以保证信号调用安全的函数叫做可重入函数
SIGCHLD 是 BSD的SIGCHLD信号,POSIX.1采用它
子进程状态改变后产生此信号,父进程需要调用一个wait函数检测发生了什么
SIGCHLD在目前的UNIX实现中是可以忽略的
首先来看看什么是信号屏蔽字:
每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集
信号集,是为了批量管理信号,在linux中,它的类型是sigset_t,大小是64bits,因为目前linux流行版本一共有64个信号(不同版本信号格式不同),我们一个bit来表示一种信号
信号集第一位就是1号信号(SIGHUP),第二位就是2号信号(SIGINT)….以此类推(我们假设位号是从1开始,而不是从0开始)。处理信号集的函数
sigemptyset 初始化信号集,全都设置为0sigfillset 初始化信号集,全都设置为1sigaddset 将一位设置为1sigdelset 关闭一位sigismember 测试一位除了SIGCHLD以外,交互式SHELL通常会处理这些信号的所有工作,比如挂起或恢复作业
linux中提供一个数组,下标是信号编号,值是信号名
signal函数由ISO C定义,是系统信号机制最简单的接口,但是signal的语义和实现有关,最好使用sigaction函数代替signal函数
进程将信号发送给其它进程需要权限,如果被发送的信号是SIGCONT,则进程可将它发送给属于同一会话的任意其它进程
检测和更改进程的信号屏蔽字
返回一个信号集,对于调用进程而言,其中的信号是阻塞不能递送的,因为也一定是未决的
signal的替代品
使程序异常终止
互斥量具有一些属性,通过修改这些属性可以控制锁的一些行为,缺省的互斥量属性如下
pshared 是否多个进程可以共享,默认是同一进程中的线程共享的type 它的具体属性如下: protocol 线程的优先级和调度相关的属性prioceiling 优先级上限属性robustness 定义了互斥锁持有者挂了怎么办读写锁也叫共享互斥锁,适合读远大于写的情况
读写锁多个线程可以同时占有读模式的锁,只有一个线程可以占用写模式的锁。有读的就不能写,有写的就不能读
读写锁也有带有超时模式的加锁函数
条件变量和互斥量一起使用时,允许线程以无竞争的方式等待特定条件发生
自旋锁等待锁的线程不是休眠,而是一个劲的忙等(自旋),自旋锁是一种偏底层的锁,通常用于实现其它类型的锁
自旋锁可以减少线程重新调度,但是占用CPU,所以只适合自旋一小段时间
是用户协调多个线程并行工作的同步机制
屏障相当于一个线,当所有合作线程到达这个线时,在继续执行
UNIX环境线程的限制: UNIX不同实现对线程的限制,不确定不代表没有! 我们可以通过sysconf函数来查询线程操作的一些限制
如果一个函数对多个线程是可重入的,就说这个函数是线程安全的。
使
当一个进程正在读或修改文件某个部分时,字节范围锁会组织其他进程修改同一文件区,字节记录锁有很多实现的方法:
fcntllockfflock共享读锁和独占性写锁:
多个读可以用一把共享读锁写锁只能加一把读写锁不能同时来就是挨个文件轮着问,浪费CPU时间,在多任务系统中应当避免
构造一个需要监听的描述符列表,然后执行一个函数,直到列表有一个函数准备好了函数再返回
可以实现I/O多路复用的函数:
selectpselectpoll描述符准备好的时候通知进程, 异步IO编写成本很高
另外我们要知道,同步IO只是相对于程序是同步的
读写多个非连续的缓冲区
能将一个磁盘文件映射到存储空间的一个缓冲区上,此时读取缓冲区就相当于读取文件,存入缓冲区就相当于存入文件。
mmap是负责存储映射的函数
IPC是各种进程通讯方式的统称
不同的UNIX实现的IPC并不完全相同
所有的UNIX实现都支持管道,半双工管道是最常用的IPC形式
管道的局限性:
大部分系统管道只支持半双工的,那么为了可移植性,我们只能认为它是半双工的管道只能在具有公共祖先的两个进程之间使用,通常两个进程是fork关系基于管道的局限性
FIFO 没有第二种局限性UNIX域套接字两种局限性都没有我们为shell命令创建一个进程,前一条命令标准输出是后一个的标准输入
管道是通过pipe函数创建的,下面是一个标准的管道
标准IO库提供了popen和pclose来建立管道协同进程
几个过滤程序通常在shell管道中线性连接,当一个过滤程序即产生某个过滤程序的输入,又读取该过滤程序的输出时,它就变成了协同进程
未命名管道只能在两个相关的进程之间使用,通过FIFO,不相关的进程也能交换数据,FIFO被叫做命名管道
创建FIFO
mkfifomkfifoat应用程序可以通过mknod和mknodat函数来创建FIFO
FIFO的用途
FIFO将数据从一条管道传送到另一条是,无需创建中间临时文件客户进程-服务器进程应用程序中,FIFO用作聚合点,在客户进程和服务器进程两者之间传递数据XSI IPC是基于System V的IPC函数的,由于XSI IPC不使用文件系统命名空间,而是构造了自己的命名空间,因而长期受批评
每个内核的IPC结构都有一个非负整数的标识符加以应用,标识符是IPC对象的内部名,每个IPC对象都与一个键关联,这个键就是它的外部名,创建IPC时都应指定一个键
消息队列是消息的链接表,存储在内核中,由消息队列标识符标识
msgget 创建一个新队列或打开一个现有队列msgsnd将新消息添加到队列尾端msgrcvy用于从队列中取消息它是一个计数器,用于为多个进程提供对共享对象的访问
允许两个或多个进程共享一个给定的存储区,这是最快的一种IPC
