unix环境多进程编程----用到的32个系统调用祥解

来源:互联网 发布:网络知识培训 编辑:程序博客网 时间:2024/06/05 18:04
 转载)unix环境多进程编程----用到的32个系统调用祥解
一.多进程程序的特点
由于UNIX系统是分时多用户系统, CPU按时间片分配给各个用户使用, 而在实质上应该说CPU按时间片分配给各个
进程使用, 每个进程都有自己的运行环境 以使得在CPU做进程切换时不会"忘记"该进程已计算了一半的"半成品".
以DOS的概念来说, 进程的切换都是一次"DOS中断"处理过程, 包括三个层次:
(1)用户数据的保存: 包括正文段(TEXT), 数据段(DATA,BSS),栈段(STACK), 共享内存段(SHARED MEMORY)的保存.
(2)寄存器数据的保存: 包括PC(program counter,指向下一条要执行的指令的地址), PSW(processor status   
word,处理机状态字), SP(stack pointer,栈指针), PCBP(pointer of process control block,进程控制块指
针), FP(frame pointer,指向栈中一个函数的local变量的首地址), AP(augument pointer,指向栈中函数调用实
参位置), ISP(interrupt stack pointer,中断栈指针), 以及其他的通用寄存器等.
(3)系统层次的保存: 包括proc,u,虚拟存储空间管理表格,中断处理栈. 以便于该进程再一次得到CPU时间片时能
  正常运行下去.
既然系统已经处理好所有这些中断处理的过程, 我们做程序还有什么要担心的呢? 我们尽可以使用系统提供的多
进程的特点, 让几个程序精诚合作,简单而又高效地把结果给它搞出来. 另外,UNIX系统本身也是用C语言写的多进
程程序,多进程编程是UNIX的特点,当我们熟悉了多进程编程后,将会对UNIX系统机制有一个较深的认识.
首先我介绍一下多进程程序的一些突出的特点:
1.并行化
一件复杂的事件是可以分解成若干个简单事件来解决的, 这在程序员的大脑中早就形成了这种概念, 首先将问题分解成一个个小问题, 将小问题再细分, 最后在一个合适的规模上做成一个函数. 在软件工程中也是这么说的. 如果我们以图的方式来思考, 一些小问题的计算是可以互不干扰的,可以同时处理, 而在关键点则需要统一在一个地方来处理, 这样程序的运行就是并行的, 至少从人的时间观念上来说是这样的. 而每个小问题的计算又是较简单的.
2.简单有序
这样的程序对程序员来说不亚于管理一班人, 程序员为每个进程设计好相应的功能, 并通过一定的通讯机制将它们有机地结合在一起, 对每个进程的设计是简单的, 只在总控部分小心应付(其实也是蛮简单的), 就可完成整个程序的施工.
3.互不干扰
这个特点是操作系统的特点, 各个进程是独立的, 不会串位.
4.事务化
比如在一个数据电话查询系统中, 将程序设计成一个进程只处理一次查询即可, 即完成一个事务. 当电话查询开始时, 产生这样一个进程对付这次查询; 另一个电话进来时, 主控程序又产生一个这样的进程对付, 每
个进程完成查询任务后消失. 这样的编程多简单, 只要做一次查询的程序就可以了.
二.常用的多进程编程的系统调用
1.fork()
功能:创建一个新的进程.
语法:#include
#include
pid_t fork();
说明:本系统调用产生一个新的进程, 叫子进程, 是调用进程的一个复制品. 调用进程叫父进程, 子进程继承了父进程的几乎所有的属性:
. 实际UID,GID和有效UID,GID.
. 环境变量.
. 附加GID.
. 调用exec()时的关闭标志.
. UID设置模式比特位.
. GID设置模式比特位.
. 进程组号.
. 会话ID.
. 控制终端.
. 当前工作目录.
. 根目录.
. 文件创建掩码UMASK.
. 文件长度限制ULIMIT.
. 预定值, 如优先级和任何其他的进程预定参数, 根据种类不同
决定是否可以继承.
. 还有一些其它属性.
但子进程也有与父进程不同的属性:
. 进程号, 子进程号不同与任何一个活动的进程组号.
. 父进程号.
. 子进程继承父进程的文件描述符或流时, 具有自己的一个拷贝
并且与父进程和其它子进程共享该资源.
. 子进程的用户时间和系统时间被初始化为0.
. 子进程的超时时钟设置为0.
. 子进程的信号处理函数指针组置为空.
. 子进程不继承父进程的记录锁.
返回值: 调用成功则对子进程返回0, 对父进程返回子进程号, 这也是最方便的区分父子进程的方法. 若调用失败则返回-1给父进程,子进程不生成.
例子:pid_t pid;
if ((pid=fork())>0) {
/*父进程处理过程*/
}
else if (pid==0) {
/*子进程处理过程*/
exit(0); /*注意子进程必须用exit()退出运行*/
}
else {
printf("fork error\n");
exit(0);
}
2.system()
功能:产生一个新的进程, 子进程执行指定的命令.
语法:#include
#include
int system(string)
char *string;
说明:本调用将参数string传递给一个命令解释器(一般为sh)执行, 即string被解释为一条命令, 由sh执行该命令.若参数string为一个空指针则为检查命令解释器是否存在. 该命令可以同命令行命令相同形式, 但由于命令做为一个参数放在系统调用中, 应注意编译时对特殊意义字符的处理. 命令的查找是按PATH环境变量的定义的. 命令所生成的后果一般不会对父进程造成影响.
返回值:当参数为空指针时, 只有当命令解释器有效时返回值为非零. 若参数不为空指针, 返回值为该命令的返回状态(同waitpid())的返回值. 命令无效或语法错误则返回非零值,所执行的命令被终止. 其他情况则返回-1.
例子:char command[81];
int i;
for (i=1;i0) {
wait((int *)0);
/*父进程等待子进程的返回*/
}
else {
/*子进程处理过程*/
exit(0);
}
7.waitpid()
功能:等待指定进程号的子进程的返回并修改状态
语法:#include
#include
pid_t waitpid(pid,stat_loc,options)
pid_t pid;
int *stat_loc,options;
说明:当pid等于-1,options等于0时,该系统调用等同于wait().否则该系统调用的行为由参数pid和options决定.
pid指定了一组父进程要求知道其状态的子进程:
-1:要求知道任何一个子进程的返回状态.
>0:要求知道进程号为pid值的子进程的状态.
0) {
waitpid(pid,&stat_loc,0);
/*父进程等待进程号为pid的子进程的返回*/
}
else {
/*子进程的处理过程*/
exit(1);
}
/*父进程*/
printf("stat_loc is [ d]\n",stat_loc);
/*字符串"stat_loc is [1]"将被打印出来*/
8.setpgrp()
功能:设置进程组号和会话号.
语法:#include
pid_t setpgrp()
说明:若调用进程不是会话首进程.将进程组号和会话号都设置为与它的进程号相等.并释放调用进程的控制终端.
返回值:调用成功后,返回新的进程组号.
例子:/*父进程处理*/
if (fork()>0) {
/*父进程处理*/
}
else {
setpgrp();
/*子进程的进程组号已修改成与它的进程号相同*/
exit(0);
}
9.exit()
功能:终止进程.
语法:#include
void exit(status)
int status;
说明:调用进程被该系统调用终止.引起附加的处理在进程被终止前全部结束.
返回值:无
10.signal()
功能:信号管理功能
语法:#include
void (*signal(sig,disp))(int)
int sig;
void (*disp)(int);
void (*sigset(sig,disp))(int)
int sig;
void (*disp)(int);
int sighold(sig)
int sig;
int sigrelse(sig)
int sig;
int sigignore(sig)
int sig;
int sigpause(sig)
int sig;
说明:这些系统调用提供了应用程序对指定信号的简单的信号处理. signal()和sigset()用于修改信号定位.参数sig指定信号(除了SIGKILL和SIGSTOP,这两种信号由系统处理,用户程序不能捕捉到). disp指定新的信号定位,即新的信号处理函数指针.可以为SIG_IGN,SIG_DFL或信号句柄地址.
若使用signal(),disp是信号句柄地址,sig不能为SIGILL,SIGTRAP或SIGPWR,收到该信号时,系统首先将重置sig的信号句柄为SIG_DFL,然后执行信号句柄.
若使用sigset(),disp是信号句柄地址,该信号时,系统首先将该信号加入调用进程的信号掩码中,然后执行信号句柄.当信号句柄运行结束后,系统将恢复调用进程的信号掩码为信号收到前的状态.另外,使用sigset()时,disp为SIG_HOLD,则该信号将会加入调用进程的信号掩码中而信号的定位不变.
sighold()将信号加入调用进程的信号掩码中.
sigrelse()将信号从调用进程的信号掩码中删除.
sigignore()将信号的定位设置为SIG_IGN.
sigpause()将信号从调用进程的信号掩码中删除,同时挂起调用进程直到收到信号.
若信号SIGCHLD的信号定位为SIG_IGN,则调用进程的子进程在终
止时不会变成僵死进程.调用进程也不用等待子进程返回并做相应处理.
返回值:调用成功则signal()返回最近调用signal()设置的disp的值. 否则返回SIG_ERR.
例子一:设置用户自己的信号中断处理函数,以SIGINT信号为例:
int flag=0;
void myself()
{
flag=1;
printf("get signal SIGINT\n");
/*若要重新设置SIGINT信号中断处理函数为本函数则执行以
*下步骤*/
void (*a)();
a=myself;
signal(SIGINT,a);
flag=2;
}
main()
{
while (1) {
sleep(2000); /*等待中断信号*/
if (flag==1) {
printf("skip system call sleep\n");
exit(0);
}
if (flag==2) {
printf("skip system call sleep\n");
printf("waiting for next signal\n");
}
}
}
11.kill()
功能:向一个或一组进程发送一个信号.
语法:#include
#include
int kill(pid,sig);
pid_t pid;
int sig;
说明:本系统调用向一个或一组进程发送一个信号,该信号由参数sig指 定,为系统给出的信号表中的一个.若为0(空信号)则检查错误但实际上并没有发送信号,用于检查pid的有效性. pid指定将要被发送信号的进程或进程组.pid若大于0,则信号将被发送到进程号等于pid的进程;若pid等于0则信号将被发送到所有的与发送信号进程同在一个进程组的进程(系统的特殊进程除外);若pid小于-1,则信号将被发送到所有进程组号与pid绝对值
相同的进程;若pid等于-1,则信号将被发送到所有的进程(特殊系统进程除外).
信号要发送到指定的进程,首先调用进程必须有对该进程发送信 号的权限.若调用进程有合适的优先级则具备有权限.若调用进程的实际或有效的UID等于接收信号的进程的实际UID或用setuid()系统调用设置的UID,或sig等于SIGCONT同时收发双方进程的会话号相同,则调用进程也有发送信号的权限.
若进程有发送信号到pid指定的任何一个进程的权限则调用成功,
否则调用失败,没有信号发出.
返回值:调用成功则返回0,否则返回-1.
例子:假设前一个例子进程号为324,现向它发一个SIGINT信号,让它做信号处理:
kill((pid_t)324,SIGINT);
12.alarm()
功能:设置一个进程的超时时钟.
语法:#include
unsigned int alarm(sec)
unsigned int sec;
说明:指示调用进程的超时时钟在指定的时间后向调用进程发送一个 SIGALRM信号.设置超时时钟时时间值不会被放入堆栈中,后一次设置会把前一次(还未到超时时间)冲掉.
若sec为0,则取消任何以前设置的超时时钟.
fork()会将新进程的超时时钟初始化为0.而当一个进程用exec()族系统调用新的执行文件时,调用前设置的超时时钟在调用后仍有效.
返回值:返回上次设置超时时钟后到调用时还剩余的时间秒数.
例子:int flag=0;
void myself()
{
flag=1;
printf("get signal SIGALRM\n");
/*若要重新设置SIGALRM信号中断处理函数为本函数则执行
*以下步骤*/
void (*a)();
a=myself;
signal(SIGALRM,a);
flag=2;
}
main()
{
alarm(100); /*100秒后发超时中断信号*/
while (1) {
sleep(2000); /*等待中断信号*/
if (flag==1) {
printf("skip system call sleep\n");
exit(0);
}
if (flag==2) {
printf("skip system call sleep\n");
printf("waiting for next signal\n");
}
}
}
13.msgsnd()
功能:发送消息到指定的消息队列中.
语法:#include
#include
#include
int msgsnd(msqid,msgp,msgsz,msgflg)
int msqid;
void *msgp;
size_t msgsz;
int msgflg;
说明:发送一个消息到由msqid指定消息队列标识号的消息队列.
参数msgp指向一个用户定义的缓冲区,并且缓冲区的第一个域应为长整型,指定消息类型,其他数据放在缓冲区的消息中其他正文区内.下面是消息元素定义:
long mtype;
char mtext[];
mtype是一个整数,用于接收进程选择消息类型.
mtext是一个长度为msgsz字节的任何正文,参数msgsz可从0到系统允许的最大值间变化.
msgflg指定操作行为:
. 若(msgflg&IPC_NOWAIT)是真的,消息并不是被立即发送而调用进程会立即返回.
. 若(msgflg&IPC_NOWAIT)不是真的,则调用进程会被挂起直到下面情况之一发生:
* 消息被发送出去.
* 消息队列标志被系统删除.系统调用返回-1.
* 调用进程接收到一个未被忽略的中断信号,调用进程继续 执行或被终止.
调用成功后,对应指定的消息队列的相关结构做如下动作:
. 消息数(msg_qnum)加1.
. 消息队列最近发送进程号(msg_lspid)改为调用进程号.
. 消息队列发送时间(msg_stime)改为当前系统时间.
以上信息可用命令ipcs -a看到.
返回值:成功则返回0,否则返回-1.
14.msgrcv()
功能:从消息队列中取得指定类型的消息.
语法:#include
#include
#include
int msgrcv(msqid,msgp,msgsz,msgtyp,msgflg)
int msqid;
void *msgp;
int msgsz;
long msgtyp;
int msgflg;
说明:本系统调用从由msqid指定的消息队列中读取一个由msgtyp指定类型的消息到由msgp指向的缓冲区中,同样的,该缓冲区的结构如前所述,包括消息类型和消息正文.msgsz为可接收的消息正文的字节数.若接收到的消息正文的长度大于msgsz,则会被截短到msgsz字节为止(当消息标志msgflg&MSG_NOERROR为真时),截掉的部份将被丢失,而且不通知消息发送进程.
msgtyp指定消息类型:
. 为0则接收消息队列中第一个消息.
. 大于0则接收消息队列中第一个类型为msgtyp的消息.
. 小于0则接收消息队列中第一个类型值不小于msgtyp绝对值且类型值又最小的消息.
msgflg指定操作行为:
. 若(msgflg&IPC_NOWAIT)是真的,调用进程会立即返回,若没有接收到消息则返回值为-1,errno设置为ENOMSG.
. 若(msgflg&IPC_NOWAIT)不是真的,则调用进程会被挂起直到下面情况之一发生:
* 队列中的消息的类型是有效的.
* 消息队列标志被系统删除.系统调用返回-1.
* 调用进程接收到一个未被忽略的中断信号,调用进程继续执行或被终止.
调用成功后,对应指定的消息队列的相关结构做如下动作:
. 消息数(msg_qnum)减1.
. 消息队列最近接收进程号(msg_lrpid)改为调用进程号.
. 消息队列接收时间(msg_rtime)改为当前系统时间.
以上信息可用命令ipcs -a看到.
返回值:调用成功则返回值等于接收到实际消息正文的字节数. 不成功则返回-1.
15.msgctl()
功能:消息控制操作
语法:#include
#include
#include
int msgctl(msqid,cmd,buf)
int msqid,cmd;
struct msqid_ds *buf;
说明:本系统调用提供一系列消息控制操作,操作动作由cmd定义,以下cmd定义值表明了各操作动作的定义.
. IPC_STAT:将msqid相关的数据结构中各个元素的当前值放入由buf指向的结构中.
. IPC_SET:将msqid相关的数据结构中的下列元素设置为由buf指向的结构中的对应值.
msg_perm.uid
msg_perm.gid
msg_perm.mode
msg_qbytes
本命令只能由有效UID等于msg_perm.cuid或msg_perm.uid的进程或有效UID有合适权限的进程操作.只有具有合适权限的用户才能增加msg_qbytes的值.
. IPC_RMID:删除由msqid指示的消息队列.将它从系统中删除并破坏相关的数据结构.
本命令只能由有效UID等于msg_perm.cuid或msg_perm.uid的进程或有效UID有合适权限的进程操作.
返回值:调用成功则返回值为0,否则为-1.
16.msgget()
功能:取得一个消息队列.
语法:#include
#include
#include
int msgget(key,msgflg)
key_t key;
int msgflg;
说明:本系统调用返回与参数key相关的消息队列的标识符.
若以下事实成立,则与消息队列相关的标识符和数据结构将被创建出来:
. 若参数key等于IPC_PRIVATE.
. 若参数key没有一个已存在的消息队列标识符与之相关,同时值(msgflg&IPC_CREAT)为真.
创建消息队列时,与新的消息队列标识符相关的数据结构将被初始化为如下:
. msg_perm.cuid和msg_perm.uid设置为调用进程的有效UID.
. msg_perm.cgid和msg_perm.gid设置为调用进程的有效GID.
. msg_perm.mode访问权限比特位设置为msgflg访问权限比特位.
. msg_qnum,msg_lspid,msg_lrpid,msg_stime,msg_rtime设置为0.
. msg_ctime设置为当前系统时间.
. msg_qbytes设置为系统允许的最大值.
返回值:调用成功则返回一非0值,称为消息队列标识符;否则返回值为-1.
例子:本例将包括上述所有消息队列操作的系统调用:
#define RKEY 0x9001L /*读消息队列的KEY值*/
#define WKEY 0x9002L /*写消息队列的KEY值*/
#define MSGFLG 0666 /*消息队列访问权限*/
#define IPC_WAIT 0 /*等待方式在include文件中未定义*/
int rmsqid; /*读消息队列标识符*/
int wmsqid; /*写消息队列标识符*/
struct msgbuf {
long mtype;
char mtext[200];
} buf;
/*若读消息队列已存在就取得标识符,否则则创建并取得标识符*/
if ((rmsqid=msgget(RKEY,MSGFLG|IPC_CREAT))0)
printf("get ld type message from queue: s\n",
buf.mtype,buf.mtext);
else {
printf("get message failed\n");
exit(3);
}
buf.mtype=3L
if (msgsnd(wmsqid,&buf,sizeof(struct msgbuf)-sizeof(long),
IPC_NOWAIT)>0)
printf("send message OK\n");
else {
printf("send message failed\n");
exit(4);
}
msgctl(wmsqid,IPC_RMID,(struct msqid *)NULL);
17.shmat()
功能:联接共享内存的操作.
语法:#include
#include
#include
void *shmat(shmid,shmaddr,shmflg)
int shmid;
void *shmaddr;
int shmid;
说明:将由shmid指示的共享内存联接到调用进程的数据段中.被联接的段放在地址,该地址由以下准则指定:
. 若shmaddr等于(void *)0,则段联接到由系统选择的第一个可用的地址上.
. 若shmaddr不等于(void *)0同时(shmflg&SHM_RND)值为真,则段联接到由(shmaddr-(shmaddr SHMLBA))给出的地址上.
. 若shmaddr不等于(void *)0同时(shmflg&SHM_RND)值为假,则段联接到由shmaddr指定的地址上.
若(shmflg&sSHM_RDONLY)为真并且调用进程有读允许,则被联接的段为只读;否则,若值不为真且调用进程有读写权限,则被联接的段为可读写的.
返回值:若调用成功则返回被联接的共享内存段在数据段上的启始地址.
否则返回值为-1.
18.shmdt()
功能:断开共享内存联接的操作.
语法:#include
#include
#include
void *shmdt(shmaddr)
void *shmaddr;
说明:本系统调用将由shmaddr指定的共享内存段从调用进程的数据段脱离出去.
返回值:若调用成功则返回值为0,否则返回值为-1.
19.shmget()
功能:取得共享内存段
语法:#include
#include
#include
int shmget(key,size,shmflg)
key_t key;
int size,shmflg;
说明:本系统调用返回key相关的共享内存标识符.
共享内存标识符和相关数据结构及至少size字节的共享内存段能正常创建,要求以下事实成立:
. 参数key等于IPC_PRIVATE.
. 参数key没有相关的共享内存标识符,同时(shmflg&IPC_CREAT)值为真.
共享内存创建时,新生成的共享内存标识相关的数据结构被初始化如下:
. shm_perm.cuid和shm_perm.uid设置为调用进程的有效UID.
. shm_perm.cgid和shm_perm.gid设置为调用进程的有效GID.
. shm_perm.mode访问权限比特位设置为shmflg访问权限比特位.
. shm_lpid,shm_nattch,shm_atime,shm_dtime设置为0.
. shm_ctime设置为当前系统时间.
. shm_segsz设置为0.
返回值:若调用成功则返回一个非0值,称为共享内存标识符,否则返回值为-1.
20.shmctl()
功能:共享内存控制操作.
语法:#include
#include
#include
int shmctl(shmid,cmd,buf)
int shmid,cmd;
struct shmid_ds *buf;
说明:本系统调用提供一系列共享内存控制操作.操作行为由cmd指定.
以下为cmd的有效值:
. IPC_STAT:将shmid相关的数据结构中各个元素的当前值放入由buf指向的结构中.
. IPC_SET:将shmid相关的数据结构中的下列元素设置为由buf指向的结构中的对应值.
shm_perm.uid
shm_perm.gid
shm_perm.mode
本命令只能由有效UID等于shm_perm.cuid或shm_perm.uid的进程或有效UID有合适权限的进程操作.
. IPC_RMID:删除由shmid指示的共享内存.将它从系统中删除并破坏相关的数据结构.
本命令只能由有效UID等于shm_perm.cuid或shm_perm.uid的进程或有效UID有合适权限的进程操作.
返回值:若调用成功则返回0,否则返回-1.
例子:本例包括上述所有共享内存操作系统调用:
#include
#include
#include
#define SHMKEY 74
#define K 1024
int shmid;
cleanup()
{
shmctl(shmid,IPC_RMID,0);
exit(0);
}
main()
{
int *pint;
char *addr1,*addr2;
extern char *shmat();
extern cleanup();
for (i=0;i0)
exit(0); /*父进程退出运行*/
/*子进程处理*/
setpgrp();
/*父进程设置的环境变量已传到子进程*/
char * value1;
value1=getenv("HOME");
value2=getenv("PATH");
printf("HOME=[ s],PATH=[ s]\n",value1,value2);
/*将打印出"HOME=/home/abcdef"和"PATH=/bin"*/

原创粉丝点击