linux进程管理的一些问题

linux进程管理的一些问题

一.进程创建
一、进程
LINUX中,进程既是一个独立拥有资源的基本单位,又是一个独立调度的基本单位。一个进程实体由若干个区(段)组成,包括程序区、数据区、栈区、共享存储区等。每个区又分为若干页,每个进程配置有唯一的进程控制块PCB,用于控制和管理进程。
PCB的数据结构如下:
1、进程表项(Process Table Entry)。包括一些最常用的核心数据:
进程标识符PID、用户标识符UID、进程状态、事件描述符、进程和U区在内存或外存的地址、软中断信号、计时域、进程的大小、偏置值nice、指向就绪队列中下一个PCB的指针P_Link、指向U区进程正文、数据及栈在内存区域的指针。
2、U区(U Area)。用于存放进程表项的一些扩充信息。
每一个进程都有一个私用的U区,其中含有:进程表项指针、真正用户标识符u-ruid(read user ID)、有效用户标识符u-euid(effective user ID)、用户文件描述符表、计时器、内部I/O参数、限制字段、差错字段、返回值、信号处理数组。
由于LINUX系统采用段页式存储管理,为了把段的起始虚地址变换为段在系统中的物理地址,便于实现区的共享,所以还有:
3、系统区表项。以存放各个段在物理存储器中的位置等信息。
系统把一个进程的虚地址空间划分为若干个连续的逻辑区,有正文区、数据区、栈区等。这些区是可被共享和保护的独立实体,多个进程可共享一个区。为了对区进行管理,核心中设置一个系统区表,各表项中记录了以下有关描述活动区的信息:
区的类型和大小、区的状态、区在物理存储器中的位置、引用计数、指向文件索引结点的指针。
4、进程区表
系统为每个进程配置了一张进程区表。表中,每一项记录一个区的起始虚地址及指向系统区表中对应的区表项。核心通过查找进程区表和系统区表,便可将区的逻辑地址变换为物理地址。
二、进程映像
LINUX系统中,进程是进程映像的执行过程,也就是正在执行的进程实体。它由三部分组成:
1、用户级上、下文。主要成分是用户程序;
2、寄存器上、下文。由CPU中的一些寄存器的内容组成,如PC,PSW,SP及通用寄存器等;
3、系统级上、下文。包括OS为管理进程所用的信息,有静态和动态之分。
三、所涉及的系统调用
1、fork( ) 
创建一个新进程。 
系统调用格式: 
pid=fork( )
参数定义:
int fork( )
fork( )返回值意义如下:
0:在子进程中,pid变量保存的fork( )返回值为0,表示当前进程是子进程。
>0:在父进程中,pid变量保存的fork( )返回值为子进程的id值(进程唯一标识符)。
-1:创建失败。
如果fork( )调用成功,它向父进程返回子进程的PID,并向子进程返回0,即fork( )被调用了一次,但返回了两次。此时OS在内存中建立一个新进程,所建的新进程是调用fork( )父进程(parent process)的副本,称为子进程(child process)。子进程继承了父进程的许多特性,并具有与父进程完全相同的用户级上下文。父进程与子进程并发执行。
核心为fork( )完成以下操作:
(1)为新进程分配一进程表项和进程标识符
进入fork( )后,核心检查系统是否有足够的资源来建立一个新进程。若资源不足,则fork( )系统调用失败;否则,核心为新进程分配一进程表项和唯一的进程标识符。
(2)检查同时运行的进程数目
超过预先规定的最大数目时,fork( )系统调用失败。
(3)拷贝进程表项中的数据
将父进程的当前目录和所有已打开的数据拷贝到子进程表项中,并置进程的状态为“创建”状态。
(4)子进程继承父进程的所有文件
对父进程当前目录和所有已打开的文件表项中的引用计数加1。
(5)为子进程创建进程上、下文
进程创建结束,设子进程状态为“内存中就绪”并返回子进程的标识符。
(6)子进程执行
虽然父进程与子进程程序完全相同,但每个进程都有自己的程序计数器PC(注意子进程的PC开始位置为fork()调用后的语句),然后根据pid变量保存的fork( )返回值的不同,执行了不同的分支语句。



例:
…..
pid=fork( );
if (! pid)
printf("I’m the child process!\n");
else if (pid>0)
printf("I’m the parent process! \n"); 
else
printf("Fork fail!\n") fork( )调用前
fork( )调用后
…..
pid=fork( );
if (! pid)
printf("I’m the child process!\n");
else if (pid>0)
printf("I’m the parent process!\n "); 
else
printf("Fork fail!\n");
…… …..
pid=fork( );
if (! pid)
printf("I’m the child process!\n");
else if (pid>0)
printf("I’m the parent process!\n ");
else
printf("Fork fail!\n");
……
四、参考程序
1、
#include <stdio.h>
main( )
{
int p1,p2;
while((p1=fork( ))= = -1); /*创建子进程p1*/
if (p1= =0) putchar(‘b’); 
else 

while((p2=fork( ))= = -1); /*创建子进程p2*/
if(p2= =0) putchar(‘c’); 
else putchar(‘a’); 
}
}
2、
#include <stdio.h>
main( )
{
int p1,p2,i;
while((p1=fork( ))= = -1); /*创建子进程p1*/
if (p1= =0)
for(i=0;i<10;i++)
printf("daughter %d\n",i);
else
{
while((p2=fork( ))= = -1); /*创建子进程p2*/
if(p2= =0)
for(i=0;i<10;i++)
printf("son %d\n",i);
else
for(i=0;i<10;i++)
printf("parent %d\n",i);
}
}
五、运行结果
1、bca,bac, abc ,……都有可能。
2、parent…
son…
daughter..
daughter..
或 parent…
son…
parent…
daughter…等
六、分析原因
除strace 外,也可用ltrace -f -i -S ./executable-file-name查看以上程序执行过程。
1、从进程并发执行来看,各种情况都有可能。上面的三个进程没有同步措施,所以父进程与子进程的输出内容会叠加在一起。输出次序带有随机性
2、由于函数printf( )在输出字符串时不会被中断,因此,字符串内部字符顺序输出不变。但由于进程并发执行的调度顺序和父子进程抢占处理机问题,输出字符串的顺序和先后随着执行的不同而发生变化。这与打印单字符的结果相同。
补充:进程树
在LINUX系统中,只有0进程是在系统引导时被创建的,在系统初启时由0进程创建1进程,以后0进程变成对换进程,1进程成为系统中的始祖进程。LINUX利用fork( )为每个终端创建一个子进程为用户服务,如等待用户登录、执行SHELL命令解释程序等,每个终端进程又可利用fork( )来创建其子进程,从而形成一棵进程树。可以说,系统中除0进程外的所有进程都是用fork( )创建的。

  

二.进程管理
lockf(files,function,size)
用作锁定文件的某些段或者整个文件。
本函数的头文件为
#include "unistd.h"
参数定义:
int lockf(files,function,size)
int files,function;
long size;
其中:files是文件描述符;function是锁定和解锁:1表示锁定,0表示解锁。size是锁定或解锁的字节数,为0,表示从文件的当前位置到文件尾。
二、参考程序
#include <stdio.h>
#include <unistd.h>
main( )
{
int p1,p2,i;
while((p1=fork( ))= = -1); /*创建子进程p1*/
if (p1= =0)
{
lockf(1,1,0); /*加锁,这里第一个参数为stdout(标准输出设备的描述符)*/
for(i=0;i<10;i++)
printf("daughter %d\n",i);
lockf(1,0,0); /*解锁*/
}
else
{
while((p2=fork( ))= =-1); /*创建子进程p2*/
if (p2= =0)
{
lockf(1,1,0); /*加锁*/
for(i=0;i<10;i++)
printf("son %d\n",i);
lockf(1,0,0); /*解锁*/
}
else
{
lockf(1,1,0); /*加锁*/
for(i=0;i<10;i++)
printf(" parent %d\n",i);
lockf(1,0,0); /*解锁*/
}
}
}
三、运行结果
parent…
son…
daughter..
daughter..
或parent…
son…
parent…
daughter…
大致与未上锁的输出结果相同,也是随着执行时间不同,输出结果的顺序有所不同。
四、分析原因
上述程序执行时,不同进程之间不存在共享临界资源(其中打印机的互斥性已由操作系统保证)问题,所以加锁与不加锁效果相同。
五、分析以下程序的输出结果:
#include<stdio.h>
#include<unistd.h>
main()
{
int p1,p2,i;
int *fp;
fp = fopen("to_be_locked.txt"
,"w+");
if(fp==NULL)
{
printf("Fail to create file");
exit(-1);
}
while((p1=fork( ))== -1); /*创建子进程p1*/
if (p1==0)
{
lockf(*fp,1,0); /*加锁*/
for(i=0;i<10;i++)
fprintf(fp,"daughter %d\n",i);
lockf(*fp,0,0); /*解锁*/
}
else
{
while((p2=fork( ))==-1); /*创建子进程p2*/
if (p2==0)
{
lockf(*fp,1,0); /*加锁*/
for(i=0;i<10;i++)
fprintf(fp,"son %d\n",i);
lockf(*fp,0,0); /*解锁*/
}
else
{
wait(NULL);
lockf(*fp,1,0); /*加锁*/
for(i=0;i<10;i++)
fprintf(fp,"parent %d\n",i);
lockf(*fp,0,0); /*解锁*/
}
}
fclose(fp);
}
cat to_be_locked.txt 查看输出结果

  

三.进程的通信
一、信号
1、信号的基本概念
每个信号都对应一个正整数常量(称为signal number,即信号编号。定义在系统头文件<signal.h>中),代表同一用户的诸进程之间传送事先约定的信息的类型,用于通知某进程发生了某异常事件。每个进程在运行时,都要通过信号机制来检查是否有信号到达。若有,便中断正在执行的程序,转向与该信号相对应的处理程序,以完成对该事件的处理;处理结束后再返回到原来的断点继续执行。实质上,信号机制是对中断机制的一种模拟,故在早期的LINUX版本中又把它称为软中断
信号与中断的相似点:
(1)采用了相同的异步通信方式;
(2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
(3)都在处理完毕后返回到原来的断点;
(4)对信号或中断都可进行屏蔽。
信号与中断的区别:
(1)中断有优先级,而信号没有优先级,所有的信号都是平等的;
(2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
(3)中断响应是及时的,而信号响应通常都有较大的时间延迟。
信号机制具有以下三方面的功能:
(1)发送信号。发送信号的程序用系统调用kill( )实现;
(2)预置对信号的处理方式。接收信号的程序用signal( )来实现对处理方式的预置;
(3)收受信号的进程按事先的规定完成对相应事件的处理。
2、信号的发送
信号的发送,是指由发送进程把信号送到指定进程的信号域的某一位上。如果目标进程正在一个可被中断的优先级上睡眠,核心便将它唤醒,发送进程就此结束。一个进程可能在其信号域中有多个位被置位,代表有多种类型的信号到达,但对于一类信号,进程却只能记住其中的某一个。
进程用kill( )向一个进程或一组进程发送一个信号。
3、对信号的处理
当一个进程要进入或退出一个低优先级睡眠状态时,或一个进程即将从核心态返回用户态时,核心都要检查该进程是否已收到软中断。当进程处于核心态时,即使收到软中断也不予理睬;只有当它返回到用户态后,才处理软中断信号。对软中断信号的处理分三种情况进行:
(1)如果进程收到的软中断是一个已决定要忽略的信号(function=1),进程不做任何处理便立即返回;
(2)进程收到软中断后便退出(function=0);
(3)执行用户设置的软中断处理程序。
二、所涉及的中断调用
1、kill( )
系统调用格式
int kill(pid,sig)
参数定义
int pid,sig;
其中,pid是一个或一组进程的标识符,参数sig是要发送的软中断信号。
(1)pid>0时,核心将信号发送给进程pid。
(2)pid=0时,核心将信号发送给与发送进程同组的所有进程。
(3)pid=-1时,核心将信号发送给所有用户标识符真正等于发送进程的有效用户标识号的进程。
2、signal( )
预置对信号的处理方式,允许调用进程控制软中断信号。
系统调用格式
signal(sig,function)
头文件为
  #include <signal.h>
参数定义
signal(sig,function)
int sig;
void (*func) ( )
其中sig用于指定信号的类型,sig为0则表示没有收到任何信号,余者如下表:

值 名 字 说 明
01 SIGHUP 挂起(hangup)
02 SIGINT 中断,当用户从键盘按^c键或^break键时
03 SIGQUIT 退出,当用户从键盘按quit键时
04 SIGILL 非法指令
05 SIGTRAP 跟踪陷阱(trace trap),启动进程,跟踪代码的执行
06 SIGIOT IOT指令
07 SIGEMT EMT指令
08 SIGFPE 浮点运算溢出
09 SIGKILL 杀死、终止进程 
10 SIGBUS 总线错误
11 SIGSEGV 段违例(segmentation violation),进程试图去访问其虚地址空间以外的位置
12 SIGSYS 系统调用中参数错,如系统调用号非法
13 SIGPIPE 向某个非读管道中写入数据
14 SIGALRM 闹钟。当某进程希望在某时间后接收信号时发此信号
15 SIGTERM 软件终止(software termination)
16 SIGUSR1 用户自定义信号1
17 SIGUSR2 用户自定义信号2
18 SIGCLD 某个子进程死
19 SIGPWR 电源故障

function:在该进程中的一个函数地址,在核心返回用户态时,它以软中断信号的序号作为参数调用该函数,对除了信号SIGKILL,SIGTRAP和SIGPWR以外的信号,核心自动地重新设置软中断信号处理程序的值为SIG_DFL,一个进程不能捕获SIGKILL信号。
function 的解释如下:
(1)function=1时,进程对sig类信号不予理睬,亦即屏蔽了该类信号;
(2)function=0时,缺省值,进程在收到sig信号后应终止自己;
(3)function为非0,非1类整数时,function的值即作为信号处理程序的指针。
三、参考程序
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void waiting( ),stop( );
int wait_mark;
main( )
{
int p1,p2,stdout;
while((p1=fork( ))= =-1); /*创建子进程p1*/
if (p1>0)
{
while((p2=fork( ))= =-1); /*创建子进程p2*/
if(p2>0)
{
wait_mark=1;
signal(SIGINT,stop); /*接收到^c信号,转stop*/
waiting( );
kill(p1,16); /*向p1发软中断信号16*/
kill(p2,17); /*向p2发软中断信号17*/
wait(0); /*同步*/
wait(0);
printf("Parent process is killed!\n");
exit(0);
}
else
{
wait_mark=1;
signal(17,stop); /*接收到软中断信号17,转stop*/
waiting( );
lockf(stdout,1,0);
printf("Child process 2 is killed by parent!\n");
lockf(stdout,0,0);
exit(0);
}
}
else
{
wait_mark=1;
signal(16,stop); /*接收到软中断信号16,转stop*/
waiting( );
lockf(stdout,1,0);
printf("Child process 1 is killed by parent!\n");
lockf(stdout,0,0);
exit(0);
}
}

void waiting( )
{
while(wait_mark!=0);
}

void stop( )
{
wait_mark=0;
}
四、运行结果
屏幕上无反应,按下^C后,显示 Parent process is killed!
五、分析原因
上述程序中,signal( )都放在一段程序的前面部位,而不是在其他接收信号处。这是因为signal( )的执行只是为进程指定信号值16或17的作用,以及分配相应的与stop( )过程链接的指针。因而,signal( )函数必须在程序前面部分执行。
本方法通信效率低,当通信数据量较大时一般不用此法。

 

/******************************************************************************************/  

三.进程通信(管道通信机制)
一、什么是管道
LINUX系统在OS的发展上,最重要的贡献之一便是该系统首创了管道(pipe)。这也是LINUX系统的一大特色。
所谓管道,是指能够连接一个写进程和一个读进程的、并允许它们以生产者—消费者方式进行通信的一个共享文件,又称为pipe文件。由写进程从管道的写入端(句柄1)将数据写入管道,而读进程则从管道的读出端(句柄0)读出数据。

句柄fd[0]
句柄fd[1] 
读出端
写入端
二、管道的类型:
1、有名管道
一个可以在文件系统中长期存在的、具有路径名的文件。用系统调用mknod( )建立。它克服无名管道使用上的局限性,可让更多的进程也能利用管道进行通信。因而其它进程可以知道它的存在,并能利用路径名来访问该文件。对有名管道的访问方式与访问其他文件一样,需先用open( )打开。
2、无名管道
一个临时文件。利用pipe( )建立起来的无名文件(无路径名)。只用该系统调用所返回的文件描述符来标识该文件,故只有调用pipe( )的进程及其子孙进程才能识别此文件描述符,才能利用该文件(管道)进行通信。当这些进程不再使用此管道时,核心收回其索引结点。
二种管道的读写方式是相同的,本文只讲无名管道。
3、pipe文件的建立
分配磁盘和内存索引结点、为读进程分配文件表项、为写进程分配文件表项、分配用户文件描述符
4、读/写进程互斥
内核为地址设置一个读指针和一个写指针,按先进先出顺序读、写。
为使读、写进程互斥地访问pipe文件,需使各进程互斥地访问pipe文件索引结点中的直接地址项。因此,每次进程在访问pipe文件前,都需检查该索引文件是否已被上锁。若是,进程便睡眠等待,否则,将其上锁,进行读/写。操作结束后解锁,并唤醒因该索引结点上锁而睡眠的进程。
三、所涉及的系统调用 
1、pipe( )
建立一无名管道。
系统调用格式
pipe(filedes)
参数定义
int pipe(filedes);
int filedes[2];
其中,filedes[1]是写入端,filedes[0]是读出端。
该函数使用头文件如下:
#include <unistd.h>
#inlcude <signal.h>
#include <stdio.h>
2、read( )
系统调用格式
read(fd,buf,nbyte)
功能:从fd所指示的文件中读出nbyte个字节的数据,并将它们送至由指针buf所指示的缓冲区中。如该文件被加锁,等待,直到锁打开为止。
参数定义
int read(fd,buf,nbyte);
int fd;
char *buf;
unsigned nbyte;
3、write( )
系统调用格式
read(fd,buf,nbyte)
功能:把nbyte 个字节的数据,从buf所指向的缓冲区写到由fd所指向的文件中。如文件加锁,暂停写入,直至开锁。
参数定义同read( )。
四、参考程序
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
int pid1,pid2;
main( )

int fd[2];
char outpipe[100],inpipe[100];
pipe(fd); /*创建一个管道*/
while ((pid1=fork( ))= =-1);
if(pid1= =0)
{
lockf(fd[1],1,0);
sprintf(outpipe,"child 1 process is sending message!"); 
/*把串放入数组outpipe中*/
write(fd[1],outpipe,50); /*向管道写长为50字节的串*/
sleep(5); /*自我阻塞5秒*/
lockf(fd[1],0,0);
exit(0);
}
else
{
while((pid2=fork( ))= =-1);
if(pid2= =0)
{ lockf(fd[1],1,0); /*互斥*/
sprintf(outpipe,"child 2 process is sending message!");
write(fd[1],outpipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
}
else
{ wait(0); /*同步*/
read(fd[0],inpipe,50); /*从管道中读长为50字节的串*/
printf("%s\n",inpipe);
wait(0);
read(fd[0],inpipe,50);
printf("%s\n",inpipe);
exit(0);
}
}
}
五、运行结果
延迟5秒后显示
child 1 process is sending message!
再延迟5秒
child 2 process is sending message!

  

进程通信(消息的发送与接受实验)
一、什么是消息
消息(message)是一个格式化的可变长的信息单元。消息机制允许由一个进程给其它任意的进程发送一个消息。当一个进程收到多个消息时,可将它们排成一个消息队列。消息使用二种重要的数据结构:一是消息首部,其中记录了一些与消息有关的信息,如消息数据的字节数;二个消息队列头表,其每一表项是作为一个消息队列的消息头,记录了消息队列的有关信息。
1、消息机制的数据结构
(1)消息首部
记录一些与消息有关的信息,如消息的类型、大小、指向消息数据区的指针、消息队列的链接指针等。
(2)消息队列头表
其每一项作为一个消息队列的消息头,记录了消息队列的有关信息如指向消息队列中第一个消息和指向最后一个消息的指针、队列中消息的数目、队列中消息数据的总字节数、队列所允许消息数据的最大字节总数,还有最近一次执行发送操作的进程标识符和时间、最近一次执行接收操作的进程标识符和时间等。
2、消息队列的描述符
LINUX中,每一个消息队列都有一个称为关键字(key)的名字,是由用户指定的;消息队列有一消息队列描述符,其作用与用户文件描述符一样,也是为了方便用户和系统对消息队列的访问。
二、涉及的系统调用
1. msgget( )
创建一个消息,获得一个消息的描述符。核心将搜索消息队列头表,确定是否有指定名字的消息队列。若无,核心将分配一新的消息队列头,并对它进行初始化,然后给用户返回一个消息队列描述符,否则它只是检查消息队列的许可权便返回。
系统调用格式:
msgqid=msgget(key,flag)
该函数使用头文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
参数定义
int msgget(key,flag)
key_t key;
int flag;
其中:
key是用户指定的消息队列的名字;flag是用户设置的标志和访问方式。如 IPC_CREAT |0400 是否该队列已被创建。无则创建,是则打开;
IPC_EXCL |0400 是否该队列的创建应是互斥的。
msgqid 是该系统调用返回的描述符,失败则返回-1。
2. msgsnd()
发送一消息。向指定的消息队列发送一个消息,并将该消息链接到该消息队列的尾部。
系统调用格式:
msgsnd(msgqid,msgp,size,flag)
该函数使用头文件如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
参数定义:
int msgsnd(msgqid,msgp,size,flag)
I int msgqid,size,flag;
struct msgbuf * msgp;
其中msgqid是返回消息队列的描述符;msgp是指向用户消息缓冲区的一个结构体指针。缓冲区中包括消息类型和消息正文,即
{
long mtype; /*消息类型*/
char mtext[ ]; /*消息的文本*/
}
size指示由msgp指向的数据结构中字符数组的长度;即消息的长度。这个数组的最大值由MSG-MAX( )系统可调用参数来确定。flag规定当核心用尽内部缓冲空间时应执行的动作:进程是等待,还是立即返回。若在标志flag中未设置IPC_NOWAIT位,则当该消息队列中的字节数超过最大值时,或系统范围的消息数超过某一最大值时,调用msgsnd进程睡眠。若是设置IPC_NOWAIT,则在此情况下,msgsnd立即返回。
对于msgsnd( ),核心须完成以下工作:
(1)对消息队列的描述符和许可权及消息长度等进行检查。若合法才继续执行,否则返回;
(2)核心为消息分配消息数据区。将用户消息缓冲区中的消息正文,拷贝到消息数据区;
(3)分配消息首部,并将它链入消息队列的末尾。在消息首部中须填写消息类型、消息大小和指向消息数据区的指针等数据;
(4)修改消息队列头中的数据,如队列中的消息数、字节总数等。最后,唤醒等待消息的进程。
3. msgrcv( )
接受一消息。从指定的消息队列中接收指定类型的消息。
系统调用格式:
msgrcv(msgqid,msgp,size,type,flag)
本函数使用的头文件如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
参数定义:
int msgrcv(msgqid,msgp,size,type,flag)
int msgqid,size,flag;
struct msgbuf *msgp;
long type;
其中,msgqid,msgp,size,flag与msgsnd中的对应参数相似,type是规定要读的消息类型,flag规定倘若该队列无消息,核心应做的操作。如此时设置了IPC_NOWAIT标志,则立即返回,若在flag中设置了MS_NOERROR,且所接收的消息大于size,则核心截断所接收的消息。
对于msgrcv系统调用,核心须完成下述工作:
(1)对消息队列的描述符和许可权等进行检查。若合法,就往下执行;否则返回;
(2)根据type的不同分成三种情况处理:
type=0,接收该队列的第一个消息,并将它返回给调用者;
type为正整数,接收类型type的第一个消息;
type为负整数,接收小于等于type绝对值的最低类型的第一个消息。
(3)当所返回消息大小等于或小于用户的请求时,核心便将消息正文拷贝到用户区,并从消息队列中删除此消息,然后唤醒睡眠的发送进程。但如果消息长度比用户要求的大时,则做出错返回。
4. msgctl( )
消息队列的操纵。读取消息队列的状态信息并进行修改,如查询消息队列描述符、修改它的许可权及删除该队列等。
系统调用格式:
msgctl(msgqid,cmd,buf);
本函数使用的头文件如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
参数定义:
int msgctl(msgqid,cmd,buf);
int msgqid,cmd;
struct msgqid_ds *buf;
其中,函数调用成功时返回0,不成功则返回-1。buf是用户缓冲区地址,供用户存放控制参数和查询结果;cmd是规定的命令。命令可分三类:
(1)IPC_STAT。查询有关消息队列情况的命令。如查询队列中的消息数目、队列中的最大字节数、最后一个发送消息的进程标识符、发送时间等;
(2)IPC_SET。按buf指向的结构中的值,设置和改变有关消息队列属性的命令。如改变消息队列的用户标识符、消息队列的许可权等;
(3)IPC_RMID。消除消息队列的标识符。
msgqid_ds 结构定义如下:
struct msgqid_ds
{ struct ipc_perm msg_perm; /*许可权结构*/
short pad1[7]; /*由系统使用*/
ushort msg_qnum; /*队列上消息数*/
ushort msg_qbytes; /*队列上最大字节数*/
ushort msg_lspid; /*最后发送消息的PID*/
ushort msg_lrpid; /*最后接收消息的PID*/
time_t msg_stime; /*最后发送消息的时间*/
time_t msg_rtime; /*最后接收消息的时间*/
time_t msg_ctime; /*最后更改时间*/
};
struct ipc_perm
{ ushort uid; /*当前用户*/
ushort gid; /*当前进程组*/
ushort cuid; /*创建用户*/
ushort cgid; /*创建进程组*/
ushort mode; /*存取许可权*/
{ short pid1; long pad2;} /*由系统使用*/ 
}
三、参考程序
1、client.c
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#define MSGKEY 75
struct msgform
{ long mtype;
char mtext[1000];
}msg;
int msgqid;

void client()
{
int i;
msgqid=msgget(MSGKEY,0777); /*打开75#消息队列*/
for(i=10;i>=1;i–)
{
msg.mtype=i;
printf(“(client)sent\n”);
msgsnd(msgqid,&msg,1024,0); /*发送消息*/
}
exit(0);
}

main( )

client( );
}

2、server.c
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#define MSGKEY 75
struct msgform
{ long mtype;
char mtext[1000];
}msg;
int msgqid;

void server( )
{
msgqid=msgget(MSGKEY,0777|IPC_CREAT); /*创建75#消息队列*/
do 
{
msgrcv(msgqid,&msg,1030,0,0); /*接收消息*/
printf(“(server)received\n”);
}while(msg.mtype!=1);
msgctl(msgqid,IPC_RMID,0); /*删除消息队列,归还资源*/
exit(0);
}

main( )

server( );
}
四、程序说明
1、为了便于操作和观察结果,编制二个程序client.c和server.c,分别用于消息的发送与接收。
2、server建立一个 Key 为75的消息队列,等待其它进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出server。server每接收到一个消息后显示一句“(server)received。”
3、client使用 key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后一个消息,即是 server端需要的结束信号。client 每发送一条消息后显示一句 “(client)sent”。
4、注意: 二个程序分别编辑、编译为client与server。执行:
./server&
ipcs -q
./client。
五、运行结果
从理想的结果来说,应当是每当client发送一个消息后,server接收该消息,client再发送下一条。也就是说“(client)sent”和 “(server)received”的字样应该在屏幕上交替出现。实际的结果大多是,先由client发送了两条消息,然后server接收一条消息。此后client 、server交替发送和接收消息。最后server一次接收两条消息。client 和server 分别发送和接收了10条消息,与预期设想一致。

  

进程通信(共享存储区通信)
一、共享存储区
1、共享存储区机制的概念
共享存储区(Share Memory)是LINUX系统中通信速度最高的一种通信机制。该机制可使若干进程共享主存中的某一个区域,且使该区域出现(映射)在多个进程的虚地址空间中。另一方面,一个进程的虚地址空间中又可连接多个共享存储区,每个共享存储区都有自己的名字。当进程间欲利用共享存储区进行通信时,必须先在主存中建立一共享存储区,然后将它附接到自己的虚地址空间上。此后,进程对该区的访问操作,与对其虚地址空间的其它部分的操作完全相同。进程之间便可通过对共享存储区中数据的读、写来进行直接通信。图示列出二个进程通过共享一个共享存储区来进行通信的例子。其中,进程A将建立的共享存储区附接到自己的AA’区域,进程B将它附接到自己的BB’区域。

进程A的虚空间 内存空间 进程B的虚空间
A
A’
应当指出,共享存储区机制只为进程提供了用于实现通信的共享存储区和对共享存储区进行操作的手段,然而并未提供对该区进行互斥访问及进程同步的措施。因而当用户需要使用该机制时,必须自己设置同步和互斥措施才能保证实现正确的通信。
二、涉及的系统调用
1、shmget( )
创建、获得一个共享存储区。
系统调用格式:
shmid=shmget(key,size,flag)
该函数使用头文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
参数定义
int shmget(key,size,flag);
key_t key;
int size,flag;
其中,key是共享存储区的名字;size是其大小(以字节计);flag是用户设置的标志,如IPC_CREAT。IPC_CREAT表示若系统中尚无指名的共享存储区,则由核心建立一个共享存储区;若系统中已有共享存储区,便忽略IPC_CREAT。
附:
操作允许权 八进制数
用户可读 00400
用户可写 00200
小组可读 00040
小组可写 00020 
其它可读 00004
其它可写 00002

控制命令 值
IPC_CREAT 0001000
IPC_EXCL 0002000
例:shmid=shmget(key,size,(IPC_CREAT|0400))
创建一个关键字为key,长度为size的共享存储区
2、shmat( )
共享存储区的附接。从逻辑上将一个共享存储区附接到进程的虚拟地址空间上。
系统调用格式:
virtaddr=shmat(shmid,addr,flag)
该函数使用头文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
参数定义
char *shmat(shmid,addr,flag);
int shmid,flag;
char * addr;
其中,shmid是共享存储区的标识符;addr是用户给定的,将共享存储区附接到进程的虚地址空间;flag规定共享存储区的读、写权限,以及系统是否应对用户规定的地址做舍入操作。其值为SHM_RDONLY时,表示只能读;其值为0时,表示可读、可写;其值为SHM_RND(取整)时,表示操作系统在必要时舍去这个地址。该系统调用的返回值是共享存储区所附接到的进程虚地址viraddr。
3、shmdt( )
把一个共享存储区从指定进程的虚地址空间断开。
系统调用格式:
shmdt(addr)
该函数使用头文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
参数定义
int shmdt(addr);
char addr;
其中,addr是要断开连接的虚地址,亦即以前由连接的系统调用shmat( )所返回的虚地址。调用成功时,返回0值,调用不成功,返回-1。
4、shmctl( )
共享存储区的控制,对其状态信息进行读取和修改。
系统调用格式:
shmctl(shmid,cmd,buf)
该函数使用头文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
参数定义
int shmctl(shmid,cmd,buf);
int shmid,cmd;
struct shmid_ds *buf;
其中,buf是用户缓冲区地址,cmd是操作命令。命令可分为多种类型:
(1)用于查询有关共享存储区的情况。如其长度、当前连接的进程数、共享区的创建者标识符等;
(2)用于设置或改变共享存储区的属性。如共享存储区的许可权、当前连接的进程计数等;
(3)对共享存储区的加锁和解锁命令;
(4)删除共享存储区标识符等。
上述的查询是将shmid所指示的数据结构中的有关成员,放入所指示的缓冲区中;而设置是用由buf所指示的缓冲区内容来设置由shmid所指示的数据结构中的相应成员。
三、参考程序
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#define SHMKEY 75
int shmid,i; int *addr;

void client( )
{ int i;
shmid=shmget(SHMKEY,1024,0777); /*打开共享存储区*/
addr=shmat(shmid,0,0); /*获得共享存储区首地址*/
for (i=9;i>=0;i–)
{ while (*addr!=-1);
printf("(client) sent\n");
*addr=i;
}
exit(0);
}

void server( )
{
shmid=shmget(SHMKEY,1024,0777|IPC_CREAT); /*创建共享存储区*/
addr=shmat(shmid,0,0); /*获取首地址*/
do 
{
*addr=-1;
while (*addr==-1);
printf("(server) received\n");
}while (*addr);
shmctl(shmid,IPC_RMID,0); /*撤消共享存储区,归还资源*/
exit(0);
}

main( )
{
while ((i=fork( ))= =-1);
if (!i) server( );
system(“ipcs -m”);
while ((i=fork( ))= =-1);
if (!i) client( );
wait(0);
wait(0);
}
四、程序说明
1、为了便于操作和观察结果,用一个程序作为“引子“,先后fork()两个子进程,server和client,进行通信。
2、server端建立一个key为75的共享区,并将第一个字节置为-1,作为数据空的标志。等待其他进程发来的消息。当该字节的值发生变化时,表示收到了信息,进行处理。然后再次把它的值设为-1,如果遇到的值为0,则视为为结束信号,取消该队列,并退出server。server每接收到一次数据后显示“(server)received”。
3、client端建立一个key为75的共享区,当共享取得第一个字节为-1时,server端空闲,可发送请求。client随即填入9到0。期间等待 server 端的再次空闲。进行完这些操作后,client退出。client每发送一次数据后显示“(client)sent”。
4、父进程在server和client均退出后结束。
五、运行结果
和预想的完全一样。但在运行过程中,发现每当client发送一次数据后,server要等待大约0.1秒才有响应。同样,之后client又需要等待大约0.1秒才发送下一个数据。
六、程序分析
出现上述应答延迟的现象是程序设计的问题。当client端发送了数据后,并没有任何措施通知server端数据已经发出,需要由client的查询才能感知。此时,client端并没有放弃系统的控制权,仍然占用CPU的时间片。只有当系统进行调度时,切换到了server进程,再进行应答。这个问题,也同样存在于server端到client的应答过程中。

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值