UNIX系统中的进程
3.1 进程标识
(1)UNIX用唯一的被称为进程ID的整数值来标识进程。每个进程有一个父进程,所以有一个父进程ID。当这个父进程被终止时,由系统的INIT进程来收养这个进程。
(2)系统管理员创建用户账户时会分配唯一的整型用户ID和整型组ID。系统通过用户ID和组ID从系统数据库中检索出允许这个用户使用的权限。权限最高的用户ID为0;是root用户。
(3)真实用户ID和真实组ID,有效用户ID和有效组ID。进程用有效ID来确定文件的访问权限。
3.2 进程状态
(1)进程的状态转换图十分的重要。
(2)进程执行I/O时是通过一个库函数去请求服务的,这个库函数有时被称为系统调用。
(3)上下文切换:将一个进程从运行状态移出,并用另一个进程来代替它的行为。
进程上下文:操作系统在上下文切换后重启进程所需的、有关此进程及其环境的信息。
可执行代码,栈,寄存器和程序计数器都是上下文的一部分。
3.3 进程的创建与fork调用
(1)进程可以通过调用fork来创建新的进程。调用进程就成为父进程,被创建的进程成为子进程。fork函数拷贝了父进程的内存映像,这样新进程就会收到父进程地址空间的一份拷贝。在fork语句调用之后,两个进程都会执行后面的指令,分别在他们自己的内存映像中执行。
fork函数的返回值是对父进程和子进程区别并执行不同代码的关键特征。fork函数向子进程返回0,将子进程的ID返回到父进程。fork失败时,他就会返回-1并设置errmo。
(2)printf 将消息写入stdout,和fprintf 将消息写入stderr 有什么区别:
系统会对Stdout缓存,特定的消息不会在printf返回之后立即出现。系统不会对stderr的消息进行缓存,而是立即写出。所以一般调试使用stderr。
fork函数通过复制父进程在内存中的映像,创建了一个新的进程。子进程继承了诸如环境和权限这样的父进程属性。子进程还集成了某些父进程资源,例如打开的和设备。
不是父进程的每个属性或资源都被子进程继承了。例如,子进程有一个系的进程ID,当然还有一个不同的父进程ID。子进程的CPU使用时间被重置为0。 子进程没有获得父进程持有的锁。父进程设置了一个警报,父进程的警报到的时候,不会通知子进程,即使在执行fork的时候,父进程有挂起的信号,子进程启动的时候也不带有挂起信号。
尽管子进程继承了它的父进程的进程优先级和调度属性,它仍然是作为一个独立的实体去和其他的进程竞争处理器时间的。在一个繁忙的分时系统上的用户可以通过创建更多的进城来获得更多的CPU时间。一个繁忙系统的系统管理员可以通过限制进程的创建来防止用户通过创建进程来获得更多的资源份额。
3.4wait函数
一个进程创建子进程时,父进程和子进程都从fork后的那个点开始继续执行。父进程可以通过执行wait或waitpid一直阻塞到子进程结束。
Wait函数的作用:会使调用者的执行挂起,直到子进程的状态成为可用的,或者调用者收到一个信号为止。
子进程的状态为可用的:子进程终止或者被停止时都有可能是可用的。
Waitpid函数:允许父进程等待一个特定的子进程,这个进程允许父进程非阻塞的检查子进程是否已经终止了。Waitpid函数有三个参数:pid、指向返回状态所在单元的指针和一个用来指定可选项的标识符。如果pid为-1,waitpid就等待任意一个子进程。Waitpid功能强大。看联机帮助。
Wait 和waitpid函数出错时返回-1, 错误值是EINTR 函数被信号中断。
Waitpid中如果最后一个参数是WNOHANG则表示无阻塞等待,即当前有子进程退出时则返回子进程的pid,没有子进程退出时则不会阻塞父进程。
Wait和waitpid两个函数的返回值:
如果等待到了子进程退出,返回子进程的pid 号;
如果出现错误就会返回-1,并设置errno.
Waitpid,使用了选项WNOHANG,waitpit返回0来报告可能有无人等待的子进程,但这些子进程的状态不可用。
考查返回值的作用在于,判断等待一个子进程,多个子进程,以及出现错误等结果的区分。
例1、等待一个子进程
pid_t cihldpid
childpid=wait(NULL);
if(childpid=-1)
printf(“waited for a child with pid%ld \n”,childpid);
例2、被信号中断后会重启的wait函数
pid_t r_wait(int *stat_loc){
int retval;
while(((retval=wait(stat_loc))==-1)&&(errno==EINTR));
return retval;
}
注释:这里的第一个条件是说明等待出错,第二个条件说明出错原因是被信号中断。
例3、用waitpid函数写的等待所有已结束的子进程,但如果没有状态可用的子进程,代码段则避免了阻塞。函数被信号中断或者成功的等到了一个子进程,它就重启waitpid。
Pid_t childpid;
while(childpid=waitpid(-1,NULL,WNOHANG))//这个条件是非零即真
if((childpid=-1)&&(errno!=EINTR))//这个条件保证不是被信号中断
break;
例4、在进程扇中等待所有子进程结束。
while(r_wait(NULL)>0) //被信号中断的处理由r_wait()来做,等待一个子进程退出的处理由while 来做。
For(;;){
childpid=wait(null);
if((childpid==-1)&&(errno!=EINTR))
break;
}
状态值
Wait和waitpid的参数stat_loc是一个指向整数变量的指针。如果stat_loca不为NULL,这些函数就将子进程的返回状态存储在这个单元中。子进程通过调用exit,_exit._Exit或从main函数中return来返回它的状态。返回值为零说明EXIT_SUCCESS;任何其它的值都说明EXIT_FAILURE。
3.5exec函数
fork函数创建了调用进程的一份拷贝,但很多应用程序都需要子进程执行与其父进程不同的代码。Exec函数簇提供了用新的映像来覆盖调用进程的进程映像的功能。
exec可以用来执行另一个编译好的程序,也可以是一个命令。
这里有六中不同形式的exec函数,
它们的区别在于:
1、命令行参数和环境变量的传递方式。
execl(execl,execlp,execle)函数会用一个显式的序列来传递命令行参数。
execv(execv,execvp和execve)函数将命令行参数放在一个参数数组中传递。
2、是否要给出可执行文件的完整路径名。
execl的参数path是一个进程映像文件的路径名,它可以是一个权限定路径名,也可以是一个相对于当前目录的路径名。Execl的第一个参数路径名,包括执行文件的名字。
execlp,第一个参数中包含一个斜杠,那么execlp就将file当作路径名,表现的和path一样,另一方面,如果file中没有斜杠,execlp就用环境变量PATH来搜寻可执行文件。
后面的参数就是所有命令行参数。
例如:
execl(“/bin/ls”,”ls”,”-l”);
execvp(argv[1],&argv[1]); argv[1]=”ls”argv[2]=”-l”;
argv[1]中不包含斜杠,所以在环境变量PATH所指定的路径中找,ls命令。
如果,包含了斜杠,则在相应的文件夹下面找。
3.6后台进程与守护进程
命令解释程序时一个用来提示命令、从标准输入中读取命令、创建子进程来执行命令并等待子进程执行完毕的一个命令解释程序。
大多数命令解释程序将一个以&结束的行解释为应该由后台进程执行的命令。命令解释程序创建了一个后台进程时,他在发出提示符并接受其他的命令行之前不用等待进程的结束。而且,从键盘键入的ctrl-C也不能终止后台进程。
例1
cd /etc
ls –l
例 2
ls –l &
守护进程:是一个通常能够无限期运行的后台进程。UNIX操作系统依靠很多守护进程来执行例行的任务。
3.7临界区
一个资源同时只能被一个进程所使用,临界资源。
每个进程中对这样的共享资源进行访问的那部分代码都被称为临界区。