学习笔记3

2013-12-05
进程都由一个非负ID唯一标识,ID0是调度进程,也被称作交换进程或系统进程。ID1通常是init进程,在自举过程结束时由内核调用,init通常读与系统有关的初始化文件(/etc/rc*文件),并将系统引导到一个状态(例如多用户),init进程不会终止。在某些UNIX虚存实现中,进程ID2是页精灵进程,负责支持虚存系统的请页操作,与交换进程一样,页精灵进程也是内核进程。
下面是一些用来返回进程标识的函数:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);   //返回:调用进程的进程ID
pid_t getppid(void);  //返回:调用进程的父进程ID
pid_t getuid(void);   //返回:调用进程的实际用户ID
pid_t geteuid(void);  //返回:调用进程的有效用户ID
pid_t getgid(void);    //返回:调用进程的实际组ID
pid_t getegid(void);  //返回:调用进程的有效组ID

fork函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
由fork创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程 ID。
子进程和父进程继续执行 fork之后的指令。子进程是父进程的复制品。例如,子进程获得父进程数据空间、堆和栈的复制品。注意,这是子进程所拥有的拷贝。父、子进程并不共享这些存储空间部分。如果正文段是只读的,则父、子进程共享正文段。
现在很多的实现并不做一个父进程数据段和堆的完全拷贝,因为在fork之后经常跟随着exec。作为替代,使用了在写时复制 ( Copy-On-Write, COW) 的技术。这些区域由父、子进程共享,而且内核将它们的存取许可权改变为只读的。如果有进程试图修改这些区域,则内核为有关部分,典型的是虚存系统中的”页“,做一个拷贝。
fork的一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程每个相同的打开描述符共享一个文件表项。
父、子进程之间的区别是:
1、fork的返回值。
2、进程ID。
3、不同的父进程ID 。
4、子进程的tms_utime,tms_stime,tms_cutime以及tms_ustime设置为0。
5、父进程设置的锁,子进程不继承。
6、子进程的未决告警被清除。
7、子进程的未决信号集设置为空集。

vfork函数
vfork函数用于创建一个新进程,这个新进程的目的是exec一个新程序。vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就不会存访该地址空间。vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。)

进程有三种正常终止方式和两种异常终止方式。
(1)正常终止:
(a)在main函数内执行return语句,这等效于调用exit函数。
(b)调用exit函数。其操作包括调用各终止处理程序(终止处理程序在调用atexit函数时登记),然后关闭所有的标准I/O流等。
(c)调用_exit系统调用函数。此函数由exit调用。
(2)异常终止:
(a)调用abort。它产生SIGABRT信号,所以是下一种异常终止的特例。
(b)当进程接收到某个信号时。进程本身、其他进程和内核都能产生传送到某一进程的信号。
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等等。
进程正常终止时,通过退出状态(传向exit或_exit的参数,或main的返回值,最后在调用_exit时转为终止状态)通知父进程它是如何终止的,而异常退出时,则由内核产生一个指示其异常终止原因的终止状态通知父进程。
当父进程先于子进程结束时,子进程会被init进程领养,从而保证了每一个进程都有一个父进程。
当终止进程的父进程调用wait或waitpid时,可以得到有关信息。这种信息至少包括进程ID、该进程的终止状态、以及该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储器,关闭其所有打开文件。在UNIX术语中,一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死进程。
在linux中,这两个函数都用于正常终止一个函数。但是函数_exit()直接是一个sys_exit系统调用,而函数exit()则通常是普通函数库中的一个函数。它会先执行一些清理操作,例如调用执行各终止处理程序、关闭所有标准IO等,然后再调用sys_exit。

wait和waitpid函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);
当一个进程正常或异常终止时,就会向父进程发送SIGCHILD信号。父进程可以选择忽略或者处理这个信号(默认是忽略)。调用wait或waitpid可能会出现三种结果:
1、阻塞(如果其所有子进程都还在运行)。
2、带子进程的终止状态立即返回(如果一个子进程已终止,正等待父进程存取其终止状态)。
3、出错立即返回(如果它没有任何子进程)。
因此可以在收到SIGCHILD信号时调用wait或waitpid,即会立即返回。
wait通过statloc返回进程状态,返回值是子进程的进程ID。
关于waitpid的pid参数:
1、pid==-1 等待任一子进程,使用这个参数使得waitpid和wait作用相同。
2、pid>0 等待其进程ID与pid相等的子进程。
3、pid==0 等待其组ID等于调用进程的组ID的任一子进程。
4、pid<-1 等待其组ID等于pid的绝对值的任一子进程。waitpid返回终止子进程的进程ID,而该子进程的终止状态则通过statloc返回。对于wait,其唯一的出错是调用进程没有子进程 (函数调用被一个信号中断时,也可能返回另一种出错)。但是对于waitpid,如果指定的进程或进程组不存在,或者调用进程没有子进程都能出错。
关于waitpid的options参数:
WNOHANG 若由pid指定的子进程并不立即可用,则waitpid不阻塞,返回0。
WUNTRACED 若某实现支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程。

###########################################################################
2013-12-06
文件描述符表:用户区的一部分,除非通过使用文件描述符的函数,否则程序无法对其进行访问。对进程中每个打开的文件,文件描述符表都包含一个条目。
系统文件表:内核区中的一部分,为系统中所有的进程共享。对每个活动的open, 它都包含一个条目。每个系统文件表的条目都包含文件偏移量、访问模式(读、写、or 读-写)以及指向它的文件描述符表的条目计数。
内存索引节点表:内核区的一部分,对系统中的每个活动的文件(被某个进程打开了),内存中索引节点表都包含一个条目。几个系统文件表条目可能对应于同一个内存索引节点表(不同进程打开同一个文件)。

wait3和wait4
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
pid_t wait3(int *statloc,int options,struct rusage *rusage);
pid_t wait4(int *statloc,int options,struct rusage *rusage);
附加参数rusage要求内核返回由终止进程及其所有子进程使用的资源摘要:用户CPU时间总量、缺页次数、接收到信号的次数等。

可变参数列表
stdarg宏
可变参数列表是通过宏来实现的,这些宏定义stdarg.h头文件,它是标准库的一部分。这个头文件声明了一个类型va_list和三个宏——va_start、va_arg、va_end。我们可以声明一个类型为va_list的变量,与这几个宏配合使用,访问参数的值。
下面的程序使用三个宏正确的完成计算指定数目的值的平均值的任务。
//指定数量的值的平均值 
#include<stdarg.h>
float(int n_values,...)
{   
 va_list   var_arg;
 int count;
 float sum=0;
 va_start(var_arg,n_values);//准备访问可变参数
 for(count=0;count<n_values;count+=1) //添加取自可变参数表的值
 {
  sum+=va_arg(var_arg,int);
 }
 va_end(var_arg);       //完成处理可变参数
 return sum/n_values;
}
注意参数列表中的省略号:它提示此处可能传递数量和类型未确定的参数。
函数声明了一个名叫var_arg的变量,它用于访问参数列表的未确定部分。这个变量通过调用va_start来初始化。它的第一个参数是va_list变量的名字,第2个参数是省略号前最后一个有名字的参数。初始化过程把var_arg变量设置为指向可变参数部分的第一个参数。
为了访问参数,需要使用va_arg,这个宏接受两个参数:va_list变量和参数列表中下一个参数的类型。在这个例子中,所有的可变参数都是整型。在有些函数中,你可能要通过前面获得的数据来判断下一个参数的类型。va_arg返回这个参数的值,并使(va_list变量)var_arg指向下一个可变参数。
最后,访问完毕最后一个参数之后,我们需要调用va_end。

###########################################################################
2013-12-09
exec函数
exec的作用是用另一个新程序替换了当前进程的正文、数据和栈段。
#include <unistd.h>
int execl(const char *pathname,const char *arg0,... /*(char *)0*/ );
int execv(const char *pathname,char *const argv[]);
int execle(const char *pathname,const char *arg0,... /*(char *)0,char *const envp[]*/ );
int execve(const char *pathname,char *const argv[],char *const envp[]);
int execlp(const char *filename,const *arg0,... /*(char *)0*/ );
int execvp(const char *filename,char *const argv[] );
上面6个函数统称为exec函数
执行exec后,进程保留了下列特征:
进程ID和父进程ID;实际用户ID和实际组ID;添加组ID;进程组ID;对话期ID;控制终端;闹钟尚余留的时间;当前工作目录;根目录;文件方式创建屏蔽字;文件锁;进程信号屏蔽;未决信号;资源限制;tms_utime,tms_stime,tms_cutime以及tms_ustime值。
而对打开文件的处理与每个描述符的exec关闭标志值(FD_CLOEXEC)有关,进程中每个打开描述符都有一个exec关闭标志,若此标志设置,则在执行exec时关闭该描述符,否则该描述符仍打开(默认是保持打开)。
在exec前后实际用户ID和实际组ID保持不变,而有效ID是否改变则取决于所执行程序的文件的设置-用户-ID位和设置-组-ID位是否设置。如果新程序的设置-用户-ID位已设置,则有效用户ID变成程序文件所有者的ID,否则有效用户ID不变。对组ID的处理方式与此相同。
在很多UNIX实现中,这六个函数中只有一个execve是内核的系统调用。另外五个只是库函数,它们最终都要调用系统调用。

更改用户ID和组ID
#include <sys/types.h>
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
(1)  若进程具有超级用户特权,则setuid函数将实际用户ID、有效用户ID,以及保存的设置-用户-ID设置为uid。
(2)  若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置-用户-ID,则setuid只将有效用户ID设置为uid。不改变实际用户ID和保存的设置-用户-ID。
(3)  如果上面两个条件都不满足,则errno设置为EPERM,并返回出错。
要注意的三点:
(1)只有超级用户进程可以更改实际用户ID 。通常,实际用户ID是在用户登录时,由login( 1 )程序设置的,而且决不会改变它。因为login是一个超级用户进程,当它调用setuid时,设置所有三个用户ID。
(2)仅当对程序文件设置了设置-用户-ID位时,exec函数设置有效用户ID。如果设置-用户-ID位没有设置,则exec函数不会改变有效用户ID,而将其维持为原先值。任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或保存的设置-用户-ID。自然,不能将有效用户ID设置为任一随机值。
(3)保存的设置-用户-ID是由exec从有效用户ID复制的。在exec按文件用户ID设置了有效用户ID后,即进行这种复制,并将此副本保存起来。
详情可参考apue表8-5。

seteuid和setegid函数
#include <sys/types.h>
#include <unistd.h>
int seteuid(uid_t uid);
int setegid(gid_t gid);
一个非特权用户可将有效用户ID设置为其实际用户ID或其保存的设置-用户-ID。对于一个特权用户则可将有效用户ID设置成uid(区别于setuid函数,它更改三个用户ID)。
还有一个setreuid(ruid,euid)函数可同时更改实际用户ID和有效用户ID。

解释器文件和解释器
以#!pathname[optional-argument]为起始行的文件为解释器文件(如#!/bin/sh),而pathname指定的即为解释器(如sh)。内核使用exec函数产生解释器进程。

system函数
#include <stdlib.h>
int system(const  char *cmdstring);
如果cmdstring是一个空指针,则仅当命令处理程序可用时,system返回非0值,这一特征可以决定在一个给定的操作系统上是否支持system函数。在UNIX中,system总是可用的。因为system在其实现中调用了fork、exec和waitpid,因此有三种返回值:
(1)如果fork 失败或者waitpid返回除EINTR之外的出错,则system返回-1,而且errno中设置了错误类型。
(2)如果exec失败(表示不能执行shell),则其返回值如同shell执行了exit(127)一样。
(3)否则所有三个函数 (fork,exec和waitpid)都成功,并且system的返回值是shell的终止状态,其格式已在waitpid中说明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值