一 linux进程模型
在传统的unix模型里有两种船舰或修改进程的操作
fork用于创建一个新的进程
execve可以在进程中用另外的程序来替换当前运行的进程

线程:提供了独立的执行线索和堆栈段  却共享数据段

二 进程属性
一个进程是一个正在执行的程序的实例,是linux基本调度单位,一个进程由如下元素组成:
程序的当前上下文,它是程序当前执行的状态
程序的当前执行目录
程序访问的文件和目录
程序的信任状态或者说访问权限,比如它的文件模式和所有权
内存和其他分配给进程的系统资源
1 进程标识号
进程号 PID
父进程号 PPID

init进程 1 是内核第一个启动的进程,init引导系统、启动守护进程并且运行必要的程序。init是所有进程的父进程。
PID常见用法之一就是创建唯一的文件名或目录
另一个典型的用途是把PID写入日志文件作为日志消息的一部分
一个进程能够用它的PPID向父进程发送信号或消息
getpid()
getppid();

2 Real和Effective标识号
<sys/types.h>
<unistd.h>

进程ID       getpid()       pid_t         
父进程ID     getppid()     pid_t
真实用户ID    getuid()    uid_t
有效用户ID    geteuid()   uid_t
真实用户组ID  getgid()  gid_t
有效用户组ID  getegid()  gid_t

3 SetUID和SetGID程序
进程的真是ID和有效ID不相同的情况是正在运行中的程序设置了setUID或setGID后才出现的。
setUID程序和setGID程序是因为其有效UID或GID被设置为文件的UID或GID而不是执行该程序的所有者或用户组的UID或GID。setUID和setGID程序的目的就是让用户能够执行具有特殊权限的程序。

passwd程序是setUID为超级用户的程序,也就是说,当执行该程序时,它的有效UID被设置为超级用户的UID,从而让它能够修改/etc/passwd文件,

4 用户和用户组信息
有两种方法能把UID转换为人们可读的名字
getlogin函数返回执行程序的用户的登录名,一旦拥有了登录名,就可以把它作为参数传递给getpwname函数,这个函数能够返回/etc/passwd文件中与登录名相应的一行完整信息。

另一种方法是把进程的UID传递给getpwuid()函数,这个函数也能返回/etc/passwd文件中恰当的条目
#include<unistd.h>
char *gelogin();

返回一个只想字符串的指针,这个字符串包含有运行该进程的用户的登录名,如果没有得到这一信息,则函数返回NULL
一旦有用了登录名,可以调用getpwnam检索相应于用户名的UID
#include<pwd.h>
struct passwd *getpwnam(const char *name);

返回的passwd结构指针指向静态分配的内存,下次调用getpwnam时会覆盖其中的内容

5 附加的进程信息
除了进程、用户和组ID之外,系统调用和库函数还能够检索进程的其他属性,比如资源利用情况和执行次数。
Wall clock time(墙上时钟时间) 是流逝的时间
User CPU time(用户CPU时间)是进程花在执行用户模式代码上的时间总量
System CPU time(系统CPU时间)是花在执行内核代码上的时间总量
通过times或getrusage可以获得这些信息
进程的资源利用情况  getrusage
#include <sys/teimes.h>
clock_t times(struct tms *buf);
times返回墙上时钟时间,buf是指向tms结构的指针,这个结构保存当前进程的时间。

#include<sys/times.h>
struct tms{
clock_t tms_utime;用户CPU时间
clock_t tms_stime;系统CPU时间
clock_t tms_cutime;孩子的用户CPU时间
clock_t tms_cstime;孩子的系统CPU时间
}

这些值都是始终滴答数而不是秒数,使用sysconf函数能够把始终滴答数转换为秒数
long tps=sysconf(_SC_CLK_TCK);
_SC_CLK_TCK
是定义每秒有多少滴答的宏,sysconf返回值的类型是long,程序用他来计算进程运行的时间有多少秒,

当程序调用system函数时,它产生一个子进程,然后是子进程而不是父进程完成所有工作并消耗了CPU时间
grep操作是I/O密集型而非CPU密集型的操作

进程的资源还包括进程的内存使用量、内存如何进行组织、进程访问内存方式的类型、进程进行I/O操作的类型和数量以及进程产生的网络活动的数量和种类。
<sys/resource.h>
rusage结构
struct rusage
{
struct timeval ru_utime;//执行用户模式代码的时间
struct timeval ru_sutime;//执行内核代码的时间
long ru_maxrss;
long ru_maxixrss;
long ru_maxidrss
long ru_maxisrss;
long ru_minflt//次要失效数(内存访问没有引起磁盘访问)
long ru_majflt;//主要失效数(内存访问引起磁盘访问)
long ru_nswap;//因主要错误而从磁盘读取的内存页数
long ru_inblock;
long ru_outblock;
long ru_msgsnd;
long ru_msgrcv;
long ru_nsiquals;
long ru_nvcsw;
long ru_nivcsw;
}

#include <sys/times.h>
#include <sys/resource.h>
#include <unistd.h>
int getrusage(int who,struct rusage *usage);
who:
RUSAGE_SELF
RUSAGE_CHILDREN

6 会话和进程组
一个进程组是相关进程的几何,这些相关进程通常是在一个管道中的命令序列。
在进程组中的所有进程都具有相同的进程组号,即PGID。

会话 是由一个或多个进程组构成的,会话领导进程是创建会话的进程。每个绘画都有唯一的标识号,成为会话ID,它只是会话领导进程的PID。会话对进程组起的作用和进程组对单个进程起的作用一样。

创建进程:
7 使用system函数
system传递给/bin/sh -c来执行string所指定的命令,string中可以包含选项和参数,接着整个命令行又传递给系统调用execve。如果没有找到/bin/sh,system返回127
#include<stdlib.h>
int system(const char *string);


#include<stdio.h>
void perror(const char *s);
perror()用来将上一个函数发生错误的原因输出到标准错误(stderr)。参数s所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。

8 fork系统调用
#include<unistd.h>
pid_t fork(void);
如果fork执行成功,就会向父进程返回子进程的PID,并向子进程返回0,这意味着即使你只调用嗯fork一次,它可以返回两次。

fork创建的新进程是和父进程一样的副本

子进程没有继承父进程的超时设置、父进程创建的文件锁,或者未决信号。

fork的异步行为意味着不能在子进程中执行以来于父进程的代码。

exec函数族
int execl(const char *path,const char *arg,.....);
int execlp(const char *file,const char *arg,.....);
int execle(const char *path,const char *arg,char *const envp[]);

int execv(const char *path,const char *const argv[]);
int execve(const char *path,const char *const argv[],char *const envp[]);
int execvp(const char *file,char *const argv[]);

execve接受3个参数:path、argv和envp。path是要执行的二进制文件或脚本的完整路径。
argv是要传递给程序的完整参数列表,包括argv[0],它一般是执行程序的名字
envp是只想用于执行execed程序的专门环境指针。

三个名字中含有l的函数希望接收以逗号分割的参数列表,列表以NULL指针作为结束标志,这些参数将床底给被执行的程序。
但是名字中包含v的函数接受一个向量,也就是指向以空结尾的字符串的指针数组。

两个以e结尾的函数  execve execle可以让你为被执行的程序创建专门的环境,这个环境保存在envp中,它也是一个指向以空结尾的字符串数组的指针,数组中每个字符串也是以空结尾的字符串数组的指针,数组中每个字符串也是以空结尾。每个字符串的形式为“name=value”,name是环境变量的名字,而value是值。
例如
char *envp[]={"PATH=/bin:/usr/bin","USR=joeblow",NULL};
其他4个函数隐式的通过全局变量environ接受他们的环境

使用putenv和getenv可以操控这些函数继承的环境
#include<stdlib.h>
int putenv(cosnt char *string);
char *getenv(const char *name);
char envval[]={"MYPATH=/usr/local/someapp/bin"};
putenv(envval);
getenv("MYPATH");

9 popen函数
popen使用管道来工作
#include<stdio.h>
FILE *popen(const char *conmmand,const char *type);
int pclose(FILE *stream);

10 等待进程--wait函数族
僵进程:在父进程有机会使用wait或waitpid手机它的退出状态之前就终止的子进程。
使用wait或waitpid调用可以收集子进程的退出状态。
#include<sys/wait.h>
#include<sys/types.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid,int *status,int options);
status保存子进程的退出状态,pid是等待进程的PID,取值:
-1 等待任何PGID等于PID的绝对值的子进程
1 等待任何子进程
0 等待任何PGID等于调用进程的子进程
》0 等待PID等于pid的子进程

options:
WNOHANG,没有子进程退出时,立即返回。
WUNTRACED,应该因为存在没有报告状态的进程而返回。

11 杀死程序
一个进程由于以下5个原因中的一个而终止:
它的main函数调用了return
它调用了exit
它调用了_exit
它调用了abort
它被一个信号终止

exit导致程序正常终止,并且返回父进程的状态,用atexit登记的函数也被执行。
_exit立即终止调用它的进程,用atexit登记的函数不被执行

#include <stdlib.h>
void abort(void);

一个进程可以使用kill函数杀死另一个进程
#include<signal.h>
#include<sys/types.h>
int kill(pid_t pid,int sig);

pid指定了要杀死的进程,而sig是要发送的信号。

12 信号
信号是由相同或不同的进程向一个进程传递的事件,信号通常用来向一个进程通知异常事件
每个信号都以SIG开头,这些名字对应于正整数常量,成为信号量<signal.h>

当进程受到一个信号后,它可以对信号采取如下三种措施之一:
忽略这个信号
捕获这个信号,这将导致执行一段成为信号处理器的代码,这叫做处理信号
允许执行信号的默认操作

信号集合是一个C数据类型sigset_t <signal.h>

发送信号:
从编程的角度看,向正在运行的程序发送信号的方法有两种:使用kill命令和使用kill函数。
kill能够用来发送出了杀死一个进程(SIGKILL,SIGTREM,SIGQUIT)之外的其他信号。

每个进程都能决定怎样响应除了SIGSTOP和SIGKILL之外的其他所有信号,而这两个信号不能被捕捉或者忽略。捕捉信号最简单的方法不是真正去捕获信号而是等待他们被发送过来。alarm函数设置了一个定时器,当定时器时间到时就发送SIGALRM信号,pause函数也有类似功能,但是它是把进程挂起直至进程受到任何信号。

#include<unistd.h>
unsigned int alarm(unisgned int seconds);
seconds是计时器时间到后时钟的秒数,如果没有设置其他超时,则返回值为0,否则返回值为前面安排的超时中保留的秒数。一个进程只能设置一次超时,把seconds设置为0就会取消前面设置。

#include<unistd.h>
int pause();
只有进程捕获到一个信号时,pause才返回调用进程。

创建一个信号集合----设置想要捕获的信号------向内核登记一个信号处理器----等候捕获信号

定义一个信号处理器
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signum);
int sigdelset(sigset_t *set,int signum);
int sigismember(const sigset_t *set,int signum);
set是一个sigset_t类型的信号集合。sigemptyset初始化信号集合set,从集合中取出所有的信号。sigfillset初始化信号集合,在集合中包含所有的信号。sigaddset把信号signum添加到信号集合中。sigdelset从几何中删除信号signum。

创建一个信号集合
使用sigemptyset或sigfillset初始化一个信号集合。如果你创建一个空的信号集合,需要使用sigaddset向感兴趣的集合中加入信号。如果创建了一个满的信号集合,需要使用sigdelset从信号掩码中删除信号。
sigset_t newset;
sigemptyset(&newset);//创建空的信号集合
sigaddset(&newset,SIGCHLD);//往信号集合中添加信号
sigismember(&newset,SIGCHLD);//检查信号集合中是否有指定信号

登记一个信号处理器
sigprocmask  设置或修改当前的信号掩码
sigaction 调用为信号或要捕获的信号登记一个信号处理器
#include<signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
how:
SIG_BLOCK set包含其他要阻塞的信号
SIG_UNBLOCK set包含要解除阻塞的信号
SIG_SETMASK set包含新的信号掩码
NULL,被忽略
如果set为NULL,当前的掩码就保存在oldset中,如果oldset为NULL,被忽略。
#include<signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
sigaction为signum所指定的信号设置信号处理器,sigaction结构描述了信号的部署。
struct sigaction{
void (*sa_hundler) (int);
sigset_t sa_mask;
int sa_flags;
void (*sa_hundler) (void);
}
sa_handler是函数指针,这个函数规定了当signum中的信号产生后,要调用的处理器或函数。
也可以为
SIG_DFL signum默认动作
SIG_IGN 忽略signum这个信号

13 检测信号
#include<signal.h>
int sigpending(sigset_t *set);
未决信号的集合在set中返回。

14 进程调度
#include<sched.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/resource.h>
int sched_setcheduler(pid_t pid,int policy,const struct sched_param *p);
int sched_get_prority_max(int policy);
int sched_get_prority_min(int policy);
int getprority(int which,int who);
int setprority(int which,int who,int prio);
int nice(int inc);
 posted on 2009-06-09 17:04  清水湾  阅读(399)  评论(0)    收藏  举报