1.UNIX基础知识
1.基本知识
- 程序调用内核的入口叫做系统调用
- GNU是一个类Unix操作系统,典型的内核就是Linux
- 用户登录信息存储于/etc/passwd
- Bourne-again shell就是bash
- 根目录的起点是"/"
2.文件和目录
- 根目录的名称是"/"
- 斜线和空格不能出现在文件名中
- 登录时的工作目录从/etc/passwd中获取
3.输入和输出
- 内核(kernel)利用文件描述符(file descriptor)来访问文件,不同进程文件描述符
相同,那么可能是同一个文件,也有可能不是 - 每当运行一个新程序时,所有shell都为其打开3个文件描述符,它们三个都是链接向终端
- 不断缓冲的IO open read write lseek close
- 标准IO 使用标准IO无需担心如何选择最佳缓冲区的大小。比如printf
4.程序和进程
- 程序是存储在磁盘某个目录上的可执行文件
- 进程控制的主要函数
- fork创建一个新进程
- exec 执行
- waitpid 父进程等待子进程终止,通过waitpid实现
- 线程ID只在它所属的进程内起作用
5.出错处理
UNIX系统函数出错时,通常会返回一个负值
错误分为致命性错误和非致命性错误
6.用户标识
- 用户ID 用户不能更改,0为根用户
- 组ID 数值,组文件时/etc/group
- 附属组ID
7.信号
用于通知进程发生了某种状况,进程可以对信号做三种处理
信号产生的方式多种多样,比如Ctrl+C,kill函数等等
8.时间
时间分为两种,日历时间(就是时间戳)和进程时间
进程时间又分为:
- 时钟时间,进程运行时间总量
- 用户CPU时间,执行用户指令所用的时间量
- 系统CPU时间,执行内核程序所经历的时间
9.系统调用和库函数
操作系统提供多种服务的入口点,由此程序向内核请求服务
UNIX所使用的技术是为每个系统调用在标准C库中设置一个具有同样名字的函数,用户进程调用这些函数,然后函数用系统所要求的技术调用对应的内核服务
系统调用和库函数的区别
- 从用户角度来看,它们区别并不重要
- 我们可以替换库函数,但是系统调用通常不能替换
- 应用程序既可调用系统调用也可以调用库函数,很多库函数还会调用系统调用
- 系统调用一般是最小接口,库函数提供比较复杂的功能
- fork exec wait通常由用户应用程序直接调用
标准化和实现
1.标准化
⑴ISO C
意图是提供C程序的可移植性,ISO C头文件依赖于操作系统所配置的C编译器版本
⑵POSIX
它定义了符合POSIX标准的操作系统必须提供的各种服务,它也包含了ISO C标准库函数,它的全名叫可移植的操作系统接口
ISO C标准如果和POSIX.1之间冲突,POSIX.1服从ISO C
POSIX标准分为必须部分和可选部分,必须不含如下:
⑶Single UNIX Specification(SUS)
POSIX接口的超集,POSIX相当于它的基本规范,只有遵循SUS标准的操作系统才能称作UNIX
2.主要实现
ISO C,POSIX,SUS只是接口的规范,主要的实现如下
- BSD 伯克利分校开发的
- Linux 类似于UNIX的操作系统
- Mac OS X 内核也是一个UNIX系统
- Solaris Sun公司开发的UNIX系统
这四种只有Mac OS X和 Solaris 10是UNIX系统,但是他们都提供UNIX编程环境,都在不同程度上符合POSIX标准
3.限制(▲)
-
编译时限制,在头文件中定义
-
运行时限制,要求进程调用一个函数获得限制值
- 与文件和目录无关的运行时限制(sysconf函数)
- 与文件或目录有关的运行时限制(pathconf和fpathconf函数)
如果一个特定的运行时限制啊在一个给定的系统中不改变,可以静态的定义在一个头文件中,如果没有定义在头文件中,应用程序就必须调用3个conf函数中的一个
⑴ISO C函数
所有编译时限制都列在头文件limits.h中
⑵POSIX限制
POSIX对ISO C进行了扩充
⑶三个运行时限制函数
若成功,返回相应值,出错返回-1
①sysconf
②pathconf
参数是路径名
③fpathconf
参数时文件描述符
⑷不确定的运行方式限制
4.选项
如果我们要编写一些可移植的应用程序,而这些程序与所有得到支持的选项有关,那么就需要一种可移植到方法以决定一种实现是否支持一个给定的选项
- 编译时选项定义在<unistd.h>中。
- 与文件或目录无关的选项用sysconf函数确定。
- 与文件或目录有关的选项通过调用pathconf或fpathconf函数来发现
* 如果符号常量的定义值为-1,那么该平台不支持相应的选项。
- 如果符号常量的定义值大于0,那么该平台支持相应的选项。
- 如果符号常量的定义值为0,则必须调用sysconf、pathconf或fpathconf以确定相应的选项是否受到支持
5.功能测试宏
头文件有POSIX.1和XSI的符号,也有具体实现自己的定义,为了防止和实现冲突,我们可以通过定义功能测试宏的方式来使用POSIX.1或者XSI的定义
6.基本系统数据类型
基本系统数据类型定义在头文件sys/types.h中,也有很多定义在其它数据类型,他们绝大多数都是_t结尾
文件IO
1.基本知识
- UNIX系统的而大多数文件IO只需要用到open,read,write,lseek,close函数,这些都是不带缓冲的IO,并非标准IO
- 文件描述符
- 大多数现代文件系统文件名最大长度255
2.open/openat/close
若成功,返回文件描述符,出错返回-1
- open
- openat 相对于open,它可以使用相对路径打开目录中的文件
返回的文件描述符一定是最小的未用描述符数值
3.creat
创建一个新文件,成功返回一个文件描述符,出错返回-1
可以被open函数替代,而且open函数可以以读写的方式打开文件,这样在创建临时文件时更方便。creat只能以只写的方式打开
4.lseek
读写操作都是从当前文件偏移量开始,打开文件除非是O_APPEND,否则是0
我们可以使用lseek给一个打开文件设置偏移量
管道,FIFO,网络套接字无法设置偏移量,我们可以用lseek来检查是否可以设置,不能设置的返回-1
设置偏移量大于文件当前长度,下次写时会形成全为0的空洞,空洞不占用磁盘存储区
5.read/write
6.进程文件共享
在Linux中,如果两个进程各自打开了同一文件:
不同的进程的vi节点是共享的,所以多个进程操作同一个文件可能会有问题。
解决方式是将定位和写合并成一个原子操作。UNIX通过O_APPEND标志来设置原子操作。
- pread 相当于lseek后调用read,但是有一定的区别
- pwrite 相当于lseek后调用write,同样有区别
9.dup和dup2
- dup 返回的文件描述符是当前可用文件描述符的最小值
- dup2 可以指定新的文件描述符
复制一个现有的文件描述符,成功返回新的文件描述符,失败返回-1
两个描述符共享相同的内部结构,共享所有的锁定,读写位置和各项权限或flags等等。例如:对一个文件描述符进行了lseek操作,另一个文件描述符的读写位置也会随之改变。不过,文件描述符之间并不共享close-on-exec flags,也就是一个关闭对另一个没有影响
10.sync fsync fdatasync
UNIX系统在内核中设有缓冲区,大多数磁盘IO都通过缓冲区进行,当写数据时,先写缓冲区,然后进入队列,最后进入磁盘
- sync 将缓冲区写入队列后返回
- fsync 只对特定文件描述符起作用,磁盘操作结束返回,确保修改过的块立即写到磁盘
- fdatasync 它只影响文件数据部分,而fsync还会同步更新文件属性
10.fcntl
- 复制一个已有的描述符
- 获取/设置文件描述符标志
- 获取/设置文件状态标志
- 获取/设置异步I/O所有权
- 获取/设置记录锁
11.ioctl
I/O操作杂物箱,很多IO操作都能用ioctl表示,主要用于终端I/O
文件和目录
1.获取文件信息
重点是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; //最后一次改变时间(指属性)
};
2.文件类型
- 普通文件 文本或者二进制
- 目录文件
- 块特殊文件 对设备(如磁盘)带缓冲的访问,块特殊文件包含被编号的块,每一块都可以独立地读取或者写入。而且可以定位于任何块,并且开始读出或写入。这些对于字符特殊文件是不可能的
- 字符特殊文件 对设备不带缓冲的访问,访问长度可变,系统所有设备要么是字符特殊文件,要么是块特殊文件
- FIFO管道 用于进程通讯
- 套接字
- socket
- 符号连接 这种类型的文件指向另一个文件
3.权限
⑴权限位
用户(读,写,执行)
组(读,写,执行)
其他(读,写,执行)
- 打开一个文件,需要对上级目录有执行权限,对文件本身有该有的权限
- 目录的执行权限位常被称为搜索位
- 目录的读权限和执行权限是由区别的
- 在目录中创建一个新文件,需要对目录有写权限和执行权限
- 在目录中删除文件,需简要对目录有写权限和执行权限,对文件不需要权限
⑵进程用户ID和设置组ID
与一个进程关联的ID至少有6个:
- 实际
- 权限
- 有效用户ID,通常为实际用户ID
- 有效组ID,通常为实际组ID
- 附属组ID
- exec函数保存
实际用户是用来标识进程是谁,是谁在执行进程,一般是登录用户;而有效用户ID则标识这该进程的访问权限在大多数情况下,实际ID和有效ID一般情况下是一样的,但是在有些情况就是不一样的了,具体可见:
Linux特殊权限位
⑶新文件和目录的所有权
当我们创建新文件时:
- 新文件用户ID为进程有效有效用户ID
- 组ID POSIX允许有下面两种方式任意一种设置,Linux默认是目录组ID,目录组ID没有则是进程组ID
⑷access/faccessat
用实际用户ID和实际组ID进行访问权限测试
⑸unmask
当我们登录系统之后创建一个文件总是有一个默认权限的,那么这个权限是怎么来的呢?这就是umask干的事情。umask设置了用户创建文件的默认权限,它与chmod的效果刚好相反,umask设置的是权限“补码”
⑹chmod/fchmod/fchmodat
修改文件权限,进程的有效用户ID必须等于文件所有者ID,或者该进程必须具有超级用户权限
⑺粘着位
- 针对目录使用
- 如果设置了粘着位,只有对该目录具有写权限的用户,并且满足下列条件之一,才能删除或重命名该目录下的文件
- 主要是对/tmp和/var/tmp使用
⑺chown/fchown/fchownat/lchown
主要用于更改文件的用户ID和组ID
4.文件长度和截断
st_size表示以字节为单位的文件长度,只针对普通文件,目录文件和符号链接有意义
EOF end-of-file表示文件结束
st_blksize 文件的块大小,文件系统给文件分配空间时的最小单位,例如512字节,这个不固定
st_blocks 文件包含的块数,文件全部内容应该根据st_size读取,而非块大小和块数相乘
文件中的空洞:
lseek函数显示地为一个打开文件设置偏移量,文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被读为0,空洞是否占用硬盘空间是由文件系统(file system)决定的
3.文件系统
⑴文件系统结构
磁盘可以分成一个或多个分区,每个分区可以包含一个文件系统
- i节点就是inode,指向磁盘上该文件存储区,i节点不包含文件名
- 目录项中包括文件名和i节点
⑵硬链接
多个文件名指向同一索引节点(Inode)就称为硬链接
硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬连接文件均被删除。
硬链接的限制:
- 连接和文件需要位于同一文件系统
- 只有超级用户才能创建指向目录的硬链接
⑶符号链接(软链接)
软链接文件有类似于Windows的快捷方式。它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。
假设符号链接没了,不会影响到原始数据。假设原始数据没了,那我这个符号链接就变成了一张空头支票,也就是悬空的符号链接
软连接可以跨文件系统,可以对目录使用
⑷文件的时间
- 最后访问时间,read
- 最后修改时间,write
- i节点状态的最后更改时间,比如chmod,chown,更改链接数等等。
系统并不维护对i节点的访问时间,所以access和stat不会影响着三个时间
⑸函数
- link/linkat 创建硬链接
- symlink/symlinkat创建符号链接
- unlink/unlinkat(解除硬链接或者软链接)
- remove
- 对于文件,remove功能与unlink相同
- 对于目录,remove功能与rmdir相同
- readlink/readlinkat·读取符号链接
- rename/renameat 重命名
- futimens,utimensat utimes 一个文件访问和修改时间
4.目录
⑴知识点
- 有适合权限的任一用户都能读该目录,只有内核才能写目录
- 一个目录的写权限位和执行权限位决定了该目录中能否创建新文件以及删除文件,它们不表示能否写目录本身
⑵函数
- mkdir/mkdirat 创建目录,rmdir 删除一个空目录,空目录是只包含.和…的目录
- chdir/fchdir 更改当前工作目录
- getcwd获取当前工作目录的绝对路径名
5.设备特殊文件
每个文件系统都构建在存储设备上,位置由主,次设备号确定
主设备号标识设备驱动程序
次设备号标识特定的子设备
比如同一个磁盘驱动器上各文件系统具有相同的主设备号,但是次设备号不同
标准I/O库
1.基础
- 不仅是UNIX,其它很多操作系统都实现了标准I/O库
- I/O函数都是围绕文件描述符的,而标准I/O库时围绕着流进行的
- 一个进程预定义了标准输入,标准输出,标准错误
- 流的定向决定了读写字符的是单字节还是多字节,当一个流最初被创建时,它并没有定向
- freopen 清除流的定向
- fwide 设置流的定向,它并不改变已定向流的定向
- 缓冲
- 全缓冲,除非进行flush,否则缓冲满进行实际IO将内容写到硬盘,除了打开至终端设备的流和标准错误以外的一般是全缓冲的
- 行缓冲,填满缓冲区或者遇到换行符进行IO,打开至终端设备的流一般是行缓冲的
- 不带缓冲,一般情况标准错误是不带缓冲的
- setbuf/setvbuf 更改缓冲类型,一定要在流已被打开后调用
- 按照一定的格式输入和输出叫格式化输入和输出
- 格式化输入主要是scanf等函数
- 格式化输出主要是printf等函数
4.打开流/关闭流
fopen/freopen/fdopen 打开流,若成功,返回文件指针,出错返回NULL
fclose 关闭流,若成功,返回0,若出错,返回EOF
5.读和写流
7.二进制IO
如果进行二进制I/O操作,我们更愿意一次读或写一个完整的结构,因为有了下面两个函数
二进制IO在跨系统时要有互认的规范模式,也就是网络协议交换二级制数据的某些技术
8.定位流
- ftell fseek 它们都嘉定文件的位置可以存放在一个长整形中
- ftello fseeko Single UNIX Specification引入
- fgetpos fsetpos IOSC引入,需要一直到非UNIX系统的应该使用它们
10.fileno
标准I/O库最终都调用I/O例程,每个标准I/O流都有一个与其相关的文件描述符,我们可以使用fileno方法来获取描述符
11.创建临时文件
- tmpnam 返回指向唯一路径名的指针
- tmpfile 返回文件指针
12.内存流
内存流只访问主存,不访问磁盘上的文件,所以性能更高
13.扩展
标准IO效率不高,每当使用fgets的时候,需要复制两次数据,一次是内核和标准IO缓冲区,第二次是标准IO缓冲区到用户程序的行缓冲区,后来有了fio,sfio,ASI等使性能大大的提升
系统数据文件
1.口令文件
加密口令存放位置/etc/passwd,里面大多数有7个字段,字段之间用冒号分割
root:*:0:0:System Administrator:/var/root:/bin/sh
我们可以看到,加密口令字段用*来代替
为了防止用户暴力破解加密口令,很多系统将加密口令存放到一个叫阴影口令/etc/shadow的文件中,里面存放了用户登录名和加密口令
阴影口令文件不是一般用户可以读取的
2.组文件/附属组ID
组文件就是维护组用户信息的地方
在很早以前,一个用户只能属于一个组,后来有了附属组ID的概念,一个用户不仅可以属于口令文件中组ID对应的组,还可以属于至多16个另外的组,这时候对文件访问权限检查时,文件组ID不仅比较进程有效组ID,还会和附属组ID比较
使用附属组ID的优点是不需要经常更改组
3.其它数据文件
一般情况下,每个数据文件都包含三个函数:
5.其它
- 登录账户信息
- utmp 记录登录到系统的各个用户
- wtmp 登录和注销事件记录
- 系统标识
进程环境
1.基本知识
- C程序总是从main函数启动
- 终止有多种方式,比如exit,调用abort等等,分为正常终止和异常终止
- 当执行一个程序时,调用EXEC的可将命令行参数传递给该新程序
- 跨函数跳转setjmp和longjmp
- 每个进程都有一组资源显示,可以使用getrlimit和setrlimit查询和修改
2.C程序的存储空间布局
- 正文段,CPU执行的机器指令部分,一般是只读可共享的
- 初始化数据段,C程序中函数之外的赋值声明
- 未初始化数据段,一般初始化为0或空指针
- 栈
- 堆
需要存放在磁盘程序文件中的段只有正文段和初始化数据段
3.共享库
共享库使得可执行文件中不再需要包含共用的库函数,而只需在所有进程都可引用的存储区保存这种库例程的一份副本,程序第一次执行或者第一次调用某个库函数时,用动态链接方法将程序与共享库函数相链接
4.存储空间分配
①ISO C标准下的
- malloc 分配指定字节数的存储区,此存储区初始值不确定
- calloc 为指定数量的对象分配存储空间,每一位都初始化为0
- realloc 修改存储区长度
- free 释放
②进阶的
5.环境变量
这里修改不会影响父进程,只会影响自己和子进程
- getenv 取其环境变量值
- putenv 以前有,就会删除以前的
- setenv 根据rewrite来判断是否删除以前的
- unsetenv 删除
进程控制
1.基本知识
- 每个进程都有一个非负整形表示其唯一进程ID,进程ID终止后过段时间可以复用,但是一般UNIX系统会稍微等等再复用
- ID为0的是系统进程,它是内核进程
- ID是1的是init进程,位于/etc/init或/sbin/init,负责在自举过后启动一个UNIX系统,它通常读取与系统有关的初始化文件/etc/rc*或/etc/inittab以及/etc/init.d,它是一个不会终止的普通用户进程
- strlen计算不包含终止null字节,sizeof会包含终止null字节
- 一个已经终止,但是其父进程还没有对它做善后处理是僵尸进程,要注意这和进程收养没有任何关系
- 父进程在子进程之前终止,子进程的父进程就会变成ID为1的init进程,init进程收养的进程如果终止,init就会调用一个wait函数来取的终止状态,防止出现僵尸进程
- system函数能够执行函数参数中的命令
- 可以通过getlogin函数获取用户的登录名
- 进程可以通过nice函数调整优先级,进程只能影响自己的NICE值
- 进程需要熟练掌握的函数
2.fork
创建的新进程为子进程,fork函数被调用后返回两次,父进程返回新建子进程ID,子进程返回ID0,这个0可不是上面所说的系统进程,子进程获得父进程数据空间,堆,栈的副本,但是他们并不共享这些存储空间部分,他们共享正文段
这里有引入了一个写时复制的概念,也就是说,fork子进程先和父进程共享存储空间,写的时候再真正做副本,有的时候父进程fork时也会将缓冲区内容复制到子进程
后来有了clone函数,可以控制共享范围
fork的一个特征是父进程的所有打开文件描述符都被赋值到子进程中,父进程和子进程每个相同的打开描述符共享一个文件表项
父进程和子进程共享同一个文件偏移量
fork的常见用法:
vfork函数用于创建一个新进程,而新进程的目的是exec一个新程序,它可以保证子进程先运行
3.wait
当一个进程终止时,内核会向父进程发送信号,这种信号的系统默认动作是忽略它
当父进程调用wait时:
- wait 阻塞到有一个子进程终止。没有子进程的会出错
- waitpid 可以指定进程号,也可以选择不阻塞
- waitid 可以指定进程组号的所有子进程
- wait3/wait4 可以返回由终止进程机器所有子进程使用的资源概况
4.exec
在执行exec函数时,如果filename包含/,则视其为路径名,否则就按PATH环境变量处理,在它所指定的各目录中搜寻可执行文件,PATH变量用:分割
大部分UNIX实现中,只有execve是内核系统的调用,另外6个只是库函数,他们最终都是调用execve函数
5.用户ID和组ID
在设计应用时,一般试图使用最小特权模型
6.解释器文件
shell脚本就是一个解释器文件
exec函数执行的是改解释器文件中第一行pathname的文件
7.进程会计
/var/account/paccount文件中会记录每个结束进程的信息,在进程结束时会按终止顺序会进行记录
acct函数用启动和禁用进程会计,会计记录写到指定的文件中:
- 在mac系统中为 /var/account/acct
- 在linux系统中为 /var/account/pacct
会计进程对应的是进程,而不是程序,如果一个进程执行三个程序,只会生成一个会计记录
8.进程时间
time函数可以获得它自己以及终止子进程的:
进程关系
1.终端登录
UNIX目前已经支持多种身份的方式,比如PAM,允许管理人员来灵活的配置登录权限,这里只说传统的终端登录方式:
- 在/etc/ttys或者/etc/inittab中,每个终端设备都有一行
- 系统自举时,内核创建进程ID为1的init进程
- init读取文件/etc/ttys或者/etc/inittab,对每一个终端设备调用一次fork
- fork出来的子进程执行getty程序
- 输出login之类的信息等待用户键入,之后调用登录程序,然后输入密码
- 登录陈宫,将工作目录更改为该用户的起始目录等
2.网络登录
系统使用伪终端的软件驱动程序,使他同一个软件既能处理终端登录,又能处理网络登录
在系统中,有一个inetd/xinetd进程来等待大多数网络连接,它等待TCP/IP连接请求,当一个连接请求到达时,它执行一次fork,然后生成的子进程来exec适当的程序
我们以telnet为例:
在上图的过程之后,telnetd进程打开一个伪终端设备,然后分成两个进程,一个处理网络连接,一个执行login程序,它们通过伪终端相连
3.进程组
通常,多个进程组合起来可以形成一个一起干活的进程组,每个进程组有个组长进程,组长进程的进程ID等于进程组ID
进程组的目的是为了完成一个任务,同时管理多个进程的启动终止等,进程有且只有一个进程组
只要某个进程组有一个进程存在,该进程组就存在,和组长进程无关,进程也可以转移到另外的进程组
4.会话
会话是一个或多个进程组的集合,当一个用户登录一次系统就形成一次会话,一个会话可包含多个进程组,但只有一个前台进程组,进程组组长进程不能创建新的会话
5.控制终端
- 一个会话可能有一个控制终端,也最多有一个控制终端
- 一个会话有一个控制终端,则它又一个前台进程组,其它进程为后台进程组
- 通常,登录时自动建立控制终端
6.作业控制
- Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成,Shell可以运行一个前台作业和任意多个后台作业,这称为作业控制。
- 作业与进程组的区别:如果作业中的某个进程又创建了子进程,则子进程不属于作业。一旦作业运行结束,Shell就把自己提到前台,如果原来的前台进程还存在(如果这个子进程还没终止),它自动变为后台进程组。
- 一个作业通常是一个进程管道
- 挂起,退出,中断只影响前台进程
- 后台作业不能读取标准输入和标准输出
7.shell执行程序
Shell调用本质其实是由shell(该系统的shell类型为bash)调用fork()函数创建一个进程,由该创建的子进程去调用一种exec函数去执行另一个程序。当子进程调用一种exec函数时,该子进程的执行的程序完全替换为新进程,因为调用exec函数并不创建新进程,所有前后进程的进程ID并不改变
8.孤儿进程组
该进程组的每个成员的父进程要么是该组的成员,要么在其它会话中
进程组1不是孤儿进程组,进程组2是孤儿进程组
9.守护进程
守护进程没有控制终端,大部分守护进程都是以超级用户特权运行
常见的守护进程
- rpcbind 将远程过程调用程序号映射为网络端口号
- cron 定时任务
处理出错消息:
syslog来处理出错的消息
守护进程的惯例:
- 锁文件存放在/var/run下面
- 配置文件在/etc/xxx.conf,而且修改配置文件对守护进程不一定生效
- 守护进程可以在/etc/inittab配置重新启动守护进程
信号
1.信号基础
- 信号是软件中断
- 信号都有名字,以SIG开头
- Linux3.2.0支持31种信号
- 信号在signal.h中被编号成正整数常量
- 不存在编号为0的信号
- 信号处理方式:
- 忽略,SIGKILL和SIGSTOP不能忽略
- 捕获,然后使用函数处理
- 执行系统默认动作,大部分系统默认动作是终止
- UNIX系统信号
2.函数signal
设置如何处理信号
在不同的实现有不同的语义,推荐使用sigaction函数
- 当执行一个程序时,所有信号的状态都是系统默认或忽略
- 进程fork时,子进程进程父进程的信号处理方式
3.中断的系统调用
早期UNIX系统如果一个进程在执行一个低速系统调用而阻塞期间收到一个信号,则系调用不再继续执行,返回出错,这个就唤醒了一个阻塞的系统调用
低速系统调用是指可能会使进程永远阻塞的一类系统调用,比如读一个数据不存在管道等等
注意:与磁盘IO相关的系统调用,只会暂时阻塞调用者,除非发生硬件错误,一般会很快返回
后来在Liunx中,当信号处理程序时signal函数时,被中断的系统调用会重启动,在sigaction中,是可选的
4.可重入函数
当一个进程正在执行malloc分配存储空间,而此时捕获信号又执行malloc,那么很可能对进程造成破坏。
在unix中,有很多可以保证信号调用安全的函数叫做可重入函数
5.SIGCHLD
SIGCHLD 是 BSD的SIGCHLD信号,POSIX.1采用它
子进程状态改变后产生此信号,父进程需要调用一个wait函数检测发生了什么
SIGCHLD在目前的UNIX实现中是可以忽略的
6.信号集
首先来看看什么是信号屏蔽字:
每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集
信号集,是为了批量管理信号,在linux中,它的类型是sigset_t,大小是64bits,因为目前linux流行版本一共有64个信号(不同版本信号格式不同),我们一个bit来表示一种信号
信号集第一位就是1号信号(SIGHUP),第二位就是2号信号(SIGINT)….以此类推(我们假设位号是从1开始,而不是从0开始)。处理信号集的函数
- sigemptyset 初始化信号集,全都设置为0
- sigfillset 初始化信号集,全都设置为1
- sigaddset 将一位设置为1
- sigdelset 关闭一位
- sigismember 测试一位
5.作业控制信号
- SIGCHLD 子进程已停止或终止
- SIGCONT 如果进程停止,则继续运行★
- SIGSTOP 停止信号
- SIGTSTP 交互式停止信号
- SIGTTIN 后台进程组成员读控制终端
- SIGTTOU 后台进程组成员写控制终端
除了SIGCHLD以外,交互式SHELL通常会处理这些信号的所有工作,比如挂起或恢复作业
6.信号名和编号
linux中提供一个数组,下标是信号编号,值是信号名
7.主要函数(待整理)
signal
signal函数由ISO C定义,是系统信号机制最简单的接口,但是signal的语义和实现有关,最好使用sigaction函数代替signal函数
kill/raise
- kill将信号发送给进程或进程组,kill的参数有多种,分别向指定进程等等发送信号
- raise允许进程向自身发送信号
进程将信号发送给其它进程需要权限,如果被发送的信号是SIGCONT,则进程可将它发送给属于同一会话的任意其它进程
alarm/pause
- alarm的作用是设置定时器,每个进程只能设置一个
- pause使进程挂起直到捕获到一个信号
sigprocmask
检测和更改进程的信号屏蔽字
sigpending
返回一个信号集,对于调用进程而言,其中的信号是阻塞不能递送的,因为也一定是未决的
sigaction
signal的替代品
sigsetjmp/siglongjmp
sigsuspend
abort
使程序异常终止
system
sleep/nanosleep/clock_nanosleep
sigqueue
线程
1.基础概念
- 就算是单核系统,多线程程序也也可以改善相应时间和吞吐量
- 线程也有ID,但只在所属进程上下文里面才有意义
- 线程ID使用pthread_t数据类型表示,因而比较线程ID相等不能仅仅按整数比较,应该调用pthread_equal函数
- 线程可以通过pthread_self函数获得自身的线程ID
- 可以调用pthread_create方法创建线程,创建线程的时候可以指定线程属性
- 新创建的线程会继承调用线程的浮点环境和信号屏蔽字,但是挂起信号集不继承
- 如果进程中的任一线程使用了exit, _Exit或者_exit,那么整个进程就终止了
- 在不终止进程的情况下线程可以有三种方式退出
- 正常结束
- 被同一进程其他线程取消
- 调用pthread_exit
- pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回
- 一个线程可以调用pthread_cancel请求终止同一进程中的另一个线程,注意是请求!!!
- 线程同进程一样,也可以有自己的退出钩子函数,而且可以有多个
- 线程和进程的比较
- unix线程的pthread有两种状态,joinable状态和unjoinable状态
- joinable状态,线程退出不会释放资源,直到调用pthread_join
- unjoinable状态,退出就释放资源
- unjoinable状态可以创建时指定,也可以调用pthread_detach函数讲状态改变成unjoinable状态,省去了给线程释放资源的麻烦
2.线程同步
⑴互斥量
互斥量具有一些属性,通过修改这些属性可以控制锁的一些行为,缺省的互斥量属性如下
- pshared 是否多个进程可以共享,默认是同一进程中的线程共享的
- type 它的具体属性如下:
- protocol 线程的优先级和调度相关的属性
- prioceiling 优先级上限属性
- robustness 定义了互斥锁持有者挂了怎么办
⑵读写锁
读写锁也叫共享互斥锁,适合读远大于写的情况
读写锁多个线程可以同时占有读模式的锁,只有一个线程可以占用写模式的锁。有读的就不能写,有写的就不能读
读写锁也有带有超时模式的加锁函数
⑶条件变量
条件变量和互斥量一起使用时,允许线程以无竞争的方式等待特定条件发生
⑷自旋锁
自旋锁等待锁的线程不是休眠,而是一个劲的忙等(自旋),自旋锁是一种偏底层的锁,通常用于实现其它类型的锁
自旋锁可以减少线程重新调度,但是占用CPU,所以只适合自旋一小段时间
⑸屏障
是用户协调多个线程并行工作的同步机制
屏障相当于一个线,当所有合作线程到达这个线时,在继续执行
线程控制
1.线程限制
UNIX环境线程的限制:
UNIX不同实现对线程的限制,不确定不代表没有!
我们可以通过sysconf函数来查询线程操作的一些限制
2.线程属性
6.线程同步属性
⑴互斥量属性
⑵读写锁属性
⑶条件变量属性
⑷屏障属性
7.重入
如果一个函数对多个线程是可重入的,就说这个函数是线程安全的。
8.线程私有数据
高级IO
1.非阻塞IO
使
2.字节范围锁
当一个进程正在读或修改文件某个部分时,字节范围锁会组织其他进程修改同一文件区,字节记录锁有很多实现的方法:
共享读锁和独占性写锁:
- 多个读可以用一把共享读锁
- 写锁只能加一把
- 读写锁不能同时来
3.多路转接
⑴轮训
就是挨个文件轮着问,浪费CPU时间,在多任务系统中应当避免
⑵IO多路转接
构造一个需要监听的描述符列表,然后执行一个函数,直到列表有一个函数准备好了函数再返回
可以实现I/O多路复用的函数:
4.异步IO
描述符准备好的时候通知进程, 异步IO编写成本很高
另外我们要知道,同步IO只是相对于程序是同步的
5.readv/writev
读写多个非连续的缓冲区
6.readn/writen
7.存储映射IO
能将一个磁盘文件映射到存储空间的一个缓冲区上,此时读取缓冲区就相当于读取文件,存入缓冲区就相当于存入文件。
mmap是负责存储映射的函数
进程通讯
1.概述
IPC是各种进程通讯方式的统称
不同的UNIX实现的IPC并不完全相同
2.管道
所有的UNIX实现都支持管道,半双工管道是最常用的IPC形式
管道的局限性:
- 大部分系统管道只支持半双工的,那么为了可移植性,我们只能认为它是半双工的
- 管道只能在具有公共祖先的两个进程之间使用,通常两个进程是fork关系
基于管道的局限性
- FIFO 没有第二种局限性
- UNIX域套接字两种局限性都没有
我们为shell命令创建一个进程,前一条命令标准输出是后一个的标准输入
管道是通过pipe函数创建的,下面是一个标准的管道
- 标准IO库提供了popen和pclose来建立管道
协同进程
几个过滤程序通常在shell管道中线性连接,当一个过滤程序即产生某个过滤程序的输入,又读取该过滤程序的输出时,它就变成了协同进程
3.FIFO
未命名管道只能在两个相关的进程之间使用,通过FIFO,不相关的进程也能交换数据,FIFO被叫做命名管道
创建FIFO
应用程序可以通过mknod和mknodat函数来创建FIFO
FIFO的用途
- FIFO将数据从一条管道传送到另一条是,无需创建中间临时文件
- 客户进程-服务器进程应用程序中,FIFO用作聚合点,在客户进程和服务器进程两者之间传递数据
4.XSI
XSI IPC是基于System V的IPC函数的,由于XSI IPC不使用文件系统命名空间,而是构造了自己的命名空间,因而长期受批评
标识符和键
每个内核的IPC结构都有一个非负整数的标识符加以应用,标识符是IPC对象的内部名,每个IPC对象都与一个键关联,这个键就是它的外部名,创建IPC时都应指定一个键
消息队列
消息队列是消息的链接表,存储在内核中,由消息队列标识符标识
- msgget 创建一个新队列或打开一个现有队列
- msgsnd将新消息添加到队列尾端
- msgrcvy用于从队列中取消息
信号量
它是一个计数器,用于为多个进程提供对共享对象的访问
共享存储
允许两个或多个进程共享一个给定的存储区,这是最快的一种IPC
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)