UNIX环境高级编程(第三版)读书笔记第二部分

2016-12-05 02:15:27

三、进程


操作系统的诞生就是为了任务管理,而进程与线程就是管理的目标。从某种意义上,操作系统的内存管理、文件系统、I/O,等等都是为了这个目标。所以这也是本书的重中之中。


3.1 文件、进程与用户

让我们先从文件说起。

文件作为一种资源,其实是要被某个进程访问的。这里有个概念要理清楚,就是用户属性和权限。

每个打开的文件,都被分配一个文件描述符,同时可以由stat通过路径和文件名或者fstat通过文件描述符获得一个文件属性结构体:

struct stat{mode_t st_mode;

ino_t st_inod;

……… },

ls -l命令即时通过这个文件结构体,获得相关的文件信息。

其中st_mode包括9各访问权限位屏蔽字,U,G,O。同时包括设置用户ID和设置组ID两个位。


每个进程,则包括6个与用户关联的ID。分别是实际用户ID,组ID。有效用户ID,组ID,附属组ID,保存的设置用户ID,设置组ID。


对于一个要被进程执行的文件,如果该文件的设置用户ID位置位,则进程执行这个文件的时候,该进程具有文件所有者用户的权限。这点要尤其注意!


一个新文件的用户ID设置为创建该文件的进程的有效用户ID。用户组ID,POSIX.1规定了两种可选的方案:要么与进程有效组ID相同,要么与所在目录的组ID。

各种实现方案各有不同,如MAC选择总是与目录组ID相同,而Linux3.2.0则根据目录的设置组ID位是否被设置分别采用不同的策略。


umask函数,设置屏蔽位。屏蔽为1的,st-mode相应位一定会被关闭。通常在shell中用umask命令可以查看该登录shell的umask。


3.2 进程


3.2.1 进程环境

让我们先来理顺一个场景。在shell中执行一个ELF文件或者脚本的时候,会发生什么?

首先,shell进程会调用fork()系统调用创建一个新的进程,然后调用exec族系统调用函数来具体执行。exec类函数会先根据文件头128个字节判断可执行文件的文件类型,然后调用相应的加载器进行文件加载,并执行。这个过程可以参考程序员的自我修养P174页。


这里说的进程环境其实主要指C程序的存储空间布局。

在一个进程的虚拟地址空间内,分布如下:


命令行参数+环境变量

共享库

BSS

初始化的数据

正文


后面两段由exec从程序文件中读入,BSS由exec初始化为0。


然后要注意ISO C的几个函数:

malloc, calloc, realloc,free,genenv, putenv, setenv


由于goto语句不能跨越函数,所以有了setjmp, longjmp。注意配对使用时,寄存器的环境得不到保存,所以尽量使用全局变量,静态变量和volatile变量,不要使用寄存器变量,auto标量。



3.2.2 进程控制

几个特殊进程:进程0(见Linux内核设计的艺术P65),进程1(init进程,/etc/inittab文件或者/etc/init.d中配置初始化文件),进程2(page daemon支持虚拟存储系统的分页操作)


fork

注意,由于UNIX没有提供一个函数可以获得所有子进程的ID,所以父进程的fork返回子进程id。子进程返回0--因为调用getppid就可以获得父进程id。

父进程与子进程不共享除正文段外的其他存储空间(子进程只拥有副本而已)

父子进程共享v节点与文件偏移量。


fork的两个常用用法:

a) 父进程复制自己,子进程处理具体请求,父进程继续等待客户端服务请求。如网络服务

b) 一个进程执行不同的程序。如shell,fork后exec


exit

exit, _Exit是C标准库的,_exit是系统调用。exit会关闭所有打开描述符,释放相关内存

父进程exit后,进程管理程序会遍历所有进程,将父进程id相关的改为1,即由init进程来收养他的子进程

子进程exit后,终止状态,CPU时间总量等依然保存。如果父进程没有wait处理,则变成僵死进程


wait与waited

wait(&stat), waited(pid, &stat, options)

wait阻塞直到第一个子进程终止,stat存放子进程终止状态


exec

execl,execv,execle,execve,execlp,execvp

l代表list,v代表参数,p代表取filename参数,e代表取envp数组


解释器文件

#! pathname [option-argument]


进程调度

nice(), getpriority(), setpriority()

nice值越大表示其越友好,优先级越低


3.2.3 进程关系


1.登录shell进程关系

init进程使用户进入多用户模式,init读取文件/etc/ttys对每个登录的终端设备,调用一次fork,它所生成的子进程则调用exec getty程序。


gettty对终端设备调用open函数,将终端打开,一旦设备打开,fd0,1,2设置到该设备上。然后getty输出login: 之类的信息。当用户键入用户名后,getty工作完成,调用Login程序如下:

execle(“/bin/login”, “login”,”-p”,username, (char *)0, envp);


login得到用户名,调用getpwnam取得用户的口令文件登录项,调用getpass显示Passwd,读密码,调用crypt将口令加密,并与pw_passwd字段相比较。如果密码错误多次,login exit(1), 父进程取得1退出信号,再次调用fork, 执行getty,对此终端重复上述过程


注意这些过程,init1的实际用户和有效用户ID都是0,即超级用户权限,所以子进程结束后,孙子进程被init1收养。这些进程用户权限都是root

如果login登录成功,则:

设置当前目录为用户其实目录

调用chown更改终端权限

设置组ID

初始化系统环境

设置用户ID,并调用该用户的登录shell


2.网络登录

init调用一个shell,由此启用守护进程Inetd,inetd进程等待网络连接。当这个shell终止,inetd被init收养

inetd等待TCP/IP连接,当一个客户端请求到达,执行fork,exec子进程请求服务相关的程序


3.进程组与会话

进程组,各进程由同一作业结合起来,接收来自同一终端的各种信号

每个进程组有一个组长进程,其组ID等于进程ID

一个进程只能为它自己或它子进程设置组ID


通常由shell管道将几个进程编成一组

如果调用setsid的进程不是一个组的组长,则创建一个新会话


与控制终端连接的会话首进程成为控制进程

会话可以包括一个前台进程组,多个后台进程组


额,困了,睡啦- -@