Chinaunix首页 | 论坛 | 博客
  • 博客访问: 6065
  • 博文数量: 2
  • 博客积分: 95
  • 博客等级: 民兵
  • 技术积分: 30
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-28 22:34
文章分类
文章存档

2006年(2)

我的朋友
最近访客

分类: C/C++

2006-04-09 19:25:56

前段时间在读前辈写的一个netflow流量采集的代码,从中学到了不少的东西,读完后把其中用到的一些关键技术总结了这么一个文档,这些知识大部分都来自网络,我只是把这些相关的只是汇总了一下.把文档中的只是对照代码,看看在实际工程中这些东西到底是怎么用的,这样学起来效果非常好.

 

1.      socket通讯

1.1.    相关知识

1.1.1. socket基本概念

Socket接口是TCP/IP网络的APISocket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。

Socket接口设计者最先是将接口放在Unix操作系统里面的。网络的Socket数据传输是一种特殊的I/OSocket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种:流式SocketSOCK_STREAM)和数据报式SocketSOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。

1.1.2. socket通讯实现方法

实现socket通讯通常要经过下面几个步骤:

1、  创建Socket

2、  Socket配置

3、  建立连接

4、  数据传输

5、  结束传输

每个步骤都有相应的标准库函数实现。下面分别对这些函数做相应介绍,这些函数和数据类型大部分都包含在头文件中。

 

创建socket

int socket(int domain, int type, int protocol);

domain:指明所使用的协议族,通常为PF_INET,表示互联网协议族(TCP/IP协议族);

type:指定socket的类型:SOCK_STREAM SOCK_DGRAMSocket接口还定义了原始SocketSOCK_RAW),允许程序使用低层协议;

protocol:通常赋值"0"Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。

Socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用Socket函数时,socket执行体将建立一个Socket,实际上"建立一个Socket"意味着为一个Socket数据结构分配存储空间。Socket执行体为你管理描述符表。

两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。Socket数据结构中包含这五种信息。

 

socket配置

通过socket调用返回一个socket描述符后,在使用socket进行网络传输以前,必须配置该socket。面向连接的socket客户端通过调用Connect函数在socket数据结构中保存本地和远端信息。无连接socket的客户端和服务端以及面向连接socket的服务端通过调用bind函数来配置本地信息。

Bind函数将socket与本机上的一个端口相关联,随后你就可以在该端口监听服务请求。Bind函数原型为:

int bind(int sockfd,struct sockaddr *my_addr, int addrlen);

Sockfd:是调用socket函数返回的socket描述符,

my_addr:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;

addrlen:常被设置为sizeof(struct sockaddr)

 

struct sockaddr结构类型是用来保存socket信息的:

struct sockaddr {

  unsigned short sa_family; /* 地址族, AF_xxx */

char sa_data[14]; /* 14 字节的协议地址 */

};

sa_family一般为AF_INET,代表InternetTCP/IP)地址族;

sa_data则包含该socketIP地址和端口号。

另外还有一种结构类型:

struct sockaddr_in {

   short int sin_family; /* 地址族 */

   unsigned short int sin_port; /* 端口号 */

   struct in_addr sin_addr; /* IP地址 */

   unsigned char sin_zero[8]; /* 填充0 以保持与struct sockaddr同样大小 */

  };

 

sin_zero用来将sockaddr_in结构填充到与struct sockaddr同样的长度,可以用bzero()memset()函数将其置为零。指向sockaddr_in 的指针和指向sockaddr的指针可以相互转换,这意味着如果一个函数所需参数类型是sockaddr时,你可以在函数调用的时候将一个指向sockaddr_in的指针转换为指向sockaddr的指针;或者相反。

 

创建连接

面向连接的客户程序使用Connect函数来配置socket并与远端服务器建立一个TCP连接,其函数原型为:

int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);

Sockfdsocket函数返回的socket描述符;

serv_addr是包含远端主机IP地址和端口号的指针;

addrlen是远端地质结构的长度。

Connect函数在出现错误时返回-1,并且设置errno为相应的错误码。进行客户端程序设计无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,socket执行体为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候到打端口。

Connect函数启动和远端主机的直接连接。只有面向连接的客户程序使用socket时才需要将此socket与远端主机相连。无连接协议从不建立直接连接。面向连接的服务器也从不启动一个连接,它只是被动的在协议端口监听客户的请求。

Listen函数使socket处于被动的监听模式,并为该socket建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。

int listen(int sockfd, int backlog);

SockfdSocket系统调用返回的socket 描述符;

backlog指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待accept()它们(参考下文)。Backlog对队列中等待服务的请求的数目进行了限制,大多数系统缺省值为20。如果一个服务请求到来时,输入队列已满,该socket将拒绝连接请求,客户将收到一个出错信息。

当出现错误时listen函数返回-1,并置相应的errno错误码。

accept()函数让服务器接收客户的连接请求。在建立好输入队列后,服务器就调用accept函数,然后睡眠并等待客户的连接请求。

 int accept(int sockfd, void *addr, int *addrlen);

sockfd是被监听的socket描述符,

addr通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息(某台主机从某个端口发出该请求);

addrten通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。出现错误时accept函数返回-1并置相应的errno值。

首先,当accept函数监视的socket收到连接请求时,socket执行体将建立一个新的socket,执行体将这个新socket和请求连接进程的地址联系起来,收到服务请求的初始socket仍可以继续在以前的 socket上监听,同时可以在新的socket描述符上进行数据传输操作。

 

数据传输

Send()recv()这两个函数用于面向连接的socket上进行数据传输。

Send()函数原型为:

 int send(int sockfd, const void *msg, int len, int flags);

Sockfd是你想用来传输数据的socket描述符;

msg是一个指向要发送数据的指针;Len是以字节为单位的数据的长度;

flags一般情况下置为0(关于该参数的用法可参照man手册)。

Send()函数返回实际上发送出的字节数,可能会少于你希望发送的数据。在程序中应该将send()的返回值与欲发送的字节数进行比较。当send()返回值与len不匹配时,应该对这种情况进行处理。

recv()函数原型为:

 int recv(int sockfd,void *buf,int len,unsigned int flags);

Sockfd是接受数据的socket描述符;

buf 是存放接收数据的缓冲区;

len是缓冲的长度。

Flags也被置为0

Recv()返回实际上接收的字节数,当出现错误时,返回-1并置相应的errno值。

 

Sendto()recvfrom()用于在无连接的数据报socket方式下进行数据传输。由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址。

sendto()函数原型为:

 int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);

to表示目地机的IP地址和端口号信息,

tolen常常被赋值为sizeof (struct sockaddr)

Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1

 

Recvfrom()函数原型为:

 int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);

from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。

fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。

Recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno

如果你对数据报socket调用了connect()函数时,你也可以利用send()recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。

 

结束传输

当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:

close(sockfd);

你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。

int shutdown(int sockfd,int how);

Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式:

·0-------不允许继续接收数据

·1-------不允许继续发送数据

·2-------不允许继续发送和接收数据,

·均为允许则调用close ()

 shutdown在操作成功时返回0,在出现错误时返回-1并置相应errno

 

其它常用函数

select()--多路同步 I/O

select() 让你可以同时监视多个套接口。如果你想知道的话,那么他就会告诉你哪个套接口准备读,哪个又 准备好了写,哪个套接口又发生了例外 (exception)

int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

这个函数监视一系列文件描述符,特别是 readfdswritefds exceptfds。如果你想知道你是否能够从标准输入和套接口描述符 sockfd 入数据,你只要将文件描述符 0 sockfd 加入到集合 readfds 中。 参数 numfds 应该等于最高的文件描述符的值加1

当函数 select() 返回的时候,readfds 的值修改为反映你选择的哪个文件 描述符可以读。你可以用下面的宏 FD_ISSET() 来测试。

相关的宏解释如下: 

FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。 

FD_SET(int fd, fd_set *fdset):建立文件句柄fdfdset的联系。 

FD_CLR(int fd, fd_set *fdset):清除文件句柄fdfdset的联系。 

FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否 

                                可读写,>0表示可读写。

 

getsockopt(取得socket状态) 

定义函数  int getsockopt(int s,int level,int optname,void* optval,socklen_t* optlen);

函数说明  getsockopt()会将参数s所指定的socket状态返回。参数optname代表欲取得何种选项状态,而参数optval则指向欲保存结果的内存地址,参数optlen则为该空间的大小。参数leveloptname请参考setsockopt()

返回值  成功则返回0,若有错误则返回-1,错误原因存于errno

错误代码  EBADF 参数s 并非合法的socket处理代码

ENOTSOCK 参数s为一文件描述词,非socket

ENOPROTOOPT 参数optname指定的选项不正确

EFAULT 参数optval指针指向无法存取的内存空间

 

 

 

2.      信号处理

2.1.    相关知识

2.1.1. 信号基本知识

信号本质

信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。

信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。

 

信号来源

信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarmsetitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。

Linux下的信号可以类比于DOS下的INT或者是Windows下的事件.在有一个信号发生时候相应的信号就会发送给相应的进程。我们使用 kill -l命令可以查看系统所拥有的信号。

 

进程对信号的响应

进程可以通过三种方式来响应一个信号:

(1) 忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILLSIGSTOP

(2) 捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;

(3) 执行缺省操作,Linux对每种信号都规定了默认操作,进程对实时信号的缺省反应是进程终止。

 

信号生命周期

从信号发送到信号处理函数的执行完毕,对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:信号诞生;信号在进程中注册完毕;信号在进程中的注销完毕;信号处理函数执行完毕。相邻两个事件的时间间隔构成信号生命周期的一个阶段。

 

 

程序中信号应用

linux下的信号应用,程序员所要做的最多只有三件事情:

1、安装信号(推荐使用sigaction());

2、实现三参数信号处理函数,handler(int signal,struct siginfo *info, void *)

3、发送信号,推荐使用sigqueue()

实际上,对有些信号来说,只要安装信号就足够了(信号处理方式采用缺省或忽略)。其他可能要做的无非是与信号集相关的几种操作。

 

2.2.2. 与信号相关的常用函数

信号的发送

发送信号的主要函数有:kill()raise() sigqueue()alarm()setitimer()以及abort()1kill()
    #include
    #include
    int kill(pid_t pid,int signo)

 

参数pid的值

信号的接收进程

pid>0

进程IDpid的进程

pid=0

同一个进程组的进程

pid<0 pid!=-1

进程组ID -pid的所有进程

pid=-1

除发送进程自身外,所有进程ID大于1的进程

 

Sinno是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。

2raise()
#include
int raise(int signo)
向进程本身发送信号,参数为即将发送的信号值。调用成功返回 0;否则,返回 -1

3sigqueue()
#include
#include
int sigqueue(pid_t pid, int sig, const union sigval val)
调用成功返回 0;否则,返回 -1

sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。

4alarm()
#include
unsigned int alarm(unsigned int seconds)
专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送

SIGALRM信号,又称为闹钟时间。进程调用alarm后,任何以前的alarm()调用都将无效。

如果参数seconds为零,那么进程内将不再包含任何闹钟时间。
返回值,如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时

间的剩余时间,否则返回0

5setitimer()
#include
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
setitimer()
alarm功能强大,支持3种类型的定时器:

ITIMER_REAL:设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程;

ITIMER_VIRTUAL 设定程序执行时间;经过指定的时间后,内核将发送SIGVTALRM信号给本进程;

ITIMER_PROF 设定进程执行以及内核因本进程而消耗的时间和,经过指定的时间后,内核将发送ITIMER_VIRTUAL信号给本进程;

Setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例,结构itimerval形式见附录1。第三个参数可不做处理。

Setitimer()调用成功返回0,否则返回-1

6abort()
#include
void abort(void);

向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。

 

信号的安装(设置信号关联动作)

如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作。

linux主要有两个函数实现信号的安装:signal()sigaction()。其中signal()在可靠信号系统调用的基础上实现, 是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue() 系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数。

1、 signal()
#include
void (*signal(int signum, void (*handler))(int)))(int);

如果该函数原型不容易理解的话,可以参考下面的分解方式来理解:
typedef void (*sighandler_t)(int)

sighandler_t signal(int signum, sighandler_t handler));

第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,可以忽略该信号(参数

设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处

理方式(参数指定一个函数地址)

如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失

败则返回SIG_ERR

2、 sigaction()
#include
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的

值,可以为除SIGKILLSIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldactNULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。

 

参数结构sigaction定义如下
struct sigaction
{
void (*sa_handler) (int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer) (void);
}
sa_handler
此参数和signal()的参数handler相同,代表新的信号处理函数。
sa_mask
用来设置在处理该信号时暂时将sa_mask 指定的信号搁置。
sa_restorer
此参数没有使用。
sa_flags
用来设置信号处理的其他相关操作,下列的数值可用。
OR
运算(|)组合
A_NOCLDSTOP :
如果参数signumSIGCHLD,则当子进程暂停时并不会通知父进程
SA_ONESHOT/SA_RESETHAND:
当调用新的信号处理函数前,将此信号处理方式改为系统预设的方式。
SA_RESTART:
被信号中断的系统调用会自行重启
SA_NOMASK/SA_NODEFER:
在处理此信号未结束前不理会此信号的再次到来。
如果参数oldact不是NULL指针,则原来的信号处理方式会由此结构sigaction 返回。

返回值:执行成功则返回0,如果有错误则返回-1

 

信号集及信号集操作函数

信号集被定义为一种数据类型:

typedef struct

{

       unsigned long sig[_NSIG_WORDS]

} sigset_t

 

信号集用来描述信号的集合,linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:

 

#include

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)

 

sigemptyset(sigset_t *set)初始化由set指定的信号集,信号集里面的所有信号被清空;

sigfillset(sigset_t *set)调用该函数后,set指向的信号集中将包含linux支持的64种信号;

sigaddset(sigset_t *set, int signum)set指向的信号集中加入signum信号;

sigdelset(sigset_t *set, int signum)set指向的信号集中删除signum信号;

sigismember(const sigset_t *set, int signum)判定信号signum是否在set指向的信号集中。

 

信号阻塞与信号未决

每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。下面是与信号阻塞相关的几个函数:

#include

int  sigprocmask(int  how,  const  sigset_t *set, sigset_t *oldset))

int sigpending(sigset_t *set));

int sigsuspend(const sigset_t *mask))

 

sigprocmask()函数能够根据参数how来实现对信号集的操作,操作主要有三种:

参数how

进程当前信号集

SIG_BLOCK

在进程当前阻塞信号集中添加set指向信号集中的信号

SIG_UNBLOCK

如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞

SIG_SETMASK

更新进程阻塞信号集为set指向的信号集

 

sigpending(sigset_t *set))获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。

sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR

 

 

 

1.      进程间通讯―共享内存

1.1.    相关知识

1.1.1. 共享内存、信号量的基本知识

共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程AB共享内存的意思是,同一块物理内存被映射到进程AB各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。

采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

下面是使用共享内存进行进程间通信的示意图:

 

使用共享内存进行进程间通信

 

共享内存

共享内存是运行在同一台机器上的进程间通信最快的方式,通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的将是实际的物理内存,在Linux系统下,这只有通过限制Linux系统存取的内存才可以做到,这当然不太实际。常用的方式是通过shmXXX函数族来实现利用共享内存进行存储的。
   首先要用的函数是shmget,它获得一个共享存储标识符。

    
#include
    
#include
    
#include
    
int shmget(key_t key, int size, int flag);
   这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存。Linux系统内核中每个IPC结构都有的一个非负整数的标识符,这样对一个消息队列发送消息时只要引用标识符就可以了。这个标识符是内核由IPC结构的关键字得到的,这个关键字,就是上面第一个函数的key。数据类型key_t是在头文件sys/types.h中定义的,它是一个长整形的数据。在我们后面的章节中,还会碰到这个关键字。

   当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中。

  
void *shmat(int shmid, void *addr, int flag);
   shmidshmget函数返回的共享存储标识符,addrflag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。

   使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCKSHM_UNLOCK等来实现。

 

信号量
   信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:
   1)测试控制该资源的信号量。

   2)若此信号量的值为正,则允许进程使用该资源。进程将进号量减1
   3)若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大

0,进程被唤醒,转入步骤(1)。
   4)当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正

在睡眠等待此信号量,则唤醒此进程。

维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/include/sys/sem.h中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget,用以获得一个信号量ID
  
#include
  
#include
  
#include
  
int semget(key_t key, int nsems, int flag);
   key是前面讲过的IPC结构的关键字,它将来决定是创建新的信号量集合,还是引用一个现有的信号量集合。nsems是该集合中的信号量数。如果是创建新集合(一般在服务器中),则必须指定nsems;如果是引用一个现有的信号量集合(一般在客户机中)则将nsems指定为0

   semctl函数用来对信号量进行操作。

  
int semctl(int semid, int semnum, int cmd, union semun arg);
   不同的操作是通过cmd参数来实现的,在头文件sem.h中定义了7种不同的操作,实际编程时可以参照使用。

   semop函数自动执行信号量集合上的操作数组。

  
int semop(int semid, struct sembuf semoparray[], size_t nops);
   semoparray是一个指针,它指向一个信号量操作数组。nops规定该数组中操作的数量。

1.1.2. 与共享内存、信号量相关的函数

shmget()

 功能:取得共享内存段

   语法:

       int shmget(key,size,shmflg)

       key_t key;

       int size,shmflg;

   说明:本系统调用返回key相关的共享内存标识符.

       共享内存标识符和相关数据结构及至少size字节的共享内存段能

       正常创建,要求以下事实成立:

       . 参数key等于IPC_PRIVATE.

       . 参数key没有相关的共享内存标识符,同时(shmflg&IPC_CREAT)

        值为真.

       共享内存创建时,新生成的共享内存标识相关的数据结构被初始化如下:

       . shm_perm.cuidshm_perm.uid设置为调用进程的有效UID.

       . shm_perm.cgidshm_perm.gid设置为调用进程的有效GID.

       . shm_perm.mode访问权限比特位设置为shmflg访问权限比特位.

       . shm_lpid,shm_nattch,shm_atime,shm_dtime设置为0.

       . shm_ctime设置为当前系统时间.

       . shm_segsz设置为0.

 返回值:若调用成功则返回一个非0,称为共享内存标识符,否则返回值为-1.

 

shmat()

功能:联接共享内存的操作.

    语法:

        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

 

shmctl()

     功能:共享内存控制操作.

        语法:

             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.cuidshm_perm.uid

                 进程或有效UID有合适权限的进程操作.

             . IPC_RMID:删除由shmid指示的共享内存.将它从系统中删除并

                 破坏相关的数据结构.

                 本命令只能由有效UID等于shm_perm.cuidshm_perm.uid

                 进程或有效UID有合适权限的进程操作.

        返回值:若调用成功则返回0,否则返回-1

 

shmdt()

 功能:断开共享内存联接的操作.

语法:

   void *shmdt(shmaddr)

     void *shmaddr;

说明:本系统调用将  shmaddr指定的共享内存段从调用进程的数据段             脱离出去.

返回值:若调用成功则返回值为0,否则返回值为-1

 

semget()

        功能:取得一组信号量.

        语法:

             int semget(key,nsems,semflg)

             key_t key;

             int nsems,semflg;

        说明:返回和key相关的信号量标识符.

             若以下事实成立,则与信号量标识符,与之相关的semid_ds数据结

             构及一组nsems信号量将被创建:

               . key等于IPC_PRIVATE.

               . 系统内还没有与key相关的信号量,同时(semflg&IPC_CREAT)

                 为真.

             创建时新的信号量相关的semid_ds数据结构被初始化如下:

             . 在操作权限结构,sem_perm.cuidsem_perm.uid设置等于调用

               进程的有效UID.

             . 在操作权限结构,sem_perm.cgidsem_perm.gid设置等于调用

               进程的有效GID.

             . 访问权限比特位sem_perm.mode设置等于semflg的访问权限比

               特位.

             . sem_otime设置等于0,sem_ctime设置等于当前系统时间.

        返回值:若调用成功,则返回一非0,称为信号量标识符;否则返回-1

 

semop()

        功能:信号量操作.

        语法:

             int semop(semid,sops,nsops)

             int semid;

             struct sembuf *sops;

             unsigned nsops;

        说明:本系统调用用于执行用户定义的在一组信号量上操作的行为集合.

             该组信号量与semid相关.

             参数sops为一个用户定义的信号量操作结构数组指针.

             参数nsops为该数组的元素个数.

             数组的每个元素结构包括如下成员:

               sem_num;    /* 信号量数 */

               sem_op;     /* 信号量操作 */

               sem_flg;    /* 操作标志 */

             由本系统调用定义的每个信号量操作是针对由semidsem_num

             定的信号量的.变量sem_op指定三种信号量操作的一种:

             . sem_op为一负数并且调用进程具有修改权限,则下列情况之

               一将会发生:

               * semval不小于sem_op的绝对值,sem_op的绝对值被减去

                 semval的值.(semflg&SEM_UNDO)为真则sem_op的绝对值加

                 上调用进程指定的信号量的semadj.

               * semval小于sem_op的绝对值同时(semflg&IPC_NOWAIT)

                 ,则本调用立即返回.

               * semval小于sem_op的绝对值同时(semflg&IPC_NOWAIT)

                 ,则本系统调用将增加指定信号量相关的semncnt(加一),

                 将调用进程挂起直到下列条件之一被满足:

                   (1).semval值变成不小于sem_op的绝对值.当这种情况发

                       生时,指定的信号量相关的semncnt减一,

                       (semflg&SEM_UNDO)为真则sem_op的绝对值加上调用

                       进程指定信号量的semadj.

                   (2).调用进程等待的semid已被系统删除.

                   (3).调用进程捕俘到信号,此时,指定信号量的semncnt

                       减一,调用进程执行中断服务程序.

             . sem_op为一正值,同时调用进程具有修改权限,sem_op的值加

               semval的值,(semflg&SEM_UNDO)为真,sem_op减去调用

               进程指定信号量的semadj.

             . sem_op0,同时调用进程具有读权限,下列情况之一将会发

               :

               * semval0,本系统调用立即返回.

               * semval不等于0(semflg&IPC_NOWAIT)为真,本系统调用

                 立即返回.

               * semval不等于0(semflg&IPC_NOWAIT)为假,本系统调用

                 将把指定信号量的

                 semzcnt值加一,将调用进程挂起直到下列情况之一发生:

                   (1).semval值变为0,指定信号量的semzcnt值减一.

                   (2).调用进程等待的semid已被系统删除.

                   (3).调用进程捕俘到信号,此时,指定信号量的semncnt

                       减一,调用进程执行中断服务程序.

        返回值:调用成功则返回0,否则返回-1

 

semctl()

        功能:信号量控制操作.

        语法:

             int semctl(semid,memnum,cmd,arg)

             int semid,semnum,cmd;

             union semun {

                   int val;

                   struct semid_ds *buf;

                   ushort *array;

             }arg;

        说明:本系统调用提供了一个信号量控制操作,操作行为由cmd定义,

             些命令是对由semidsemnum指定的信号量做操作的.每个命令都

             要求有相应的权限级别:

             . GETVAL:返回semval的值,要求有读权限.

             . SETVAL:设置semval的值到arg.val.此命令成功执行后,

                  semadj的值对应的所有进程的信号量全部被清除,要求有修

                  改权限.

             . GETPID:返回sempid的值,要求有读权限.

             . GETNCNT:返回semncnt的值,要求有读权限.

             . GETZCNT:返回semzcnt的值,要求有读权限.

 

             以下命令在一组信号量中的各个semval上操作:

              . GETALL:返回每个semval的值,同时将各个值放入由arg.array

                 指向的数组中.当此命令成功执行后,semadj的值对应的所有

                 进程的信号量全部被清除,要求有修改权限.

             . SETALL:根据由arg.array指向的数组设置各个semval.当此

                 命令成功执行后,semadj的值对应的所有进程的信号量全部

                 被清除,要求有修改权限.

 

             以下命令在任何情况下都是有效的:

             . IPC_STAT:将与semid相关的数据结构的各个成员的值放入由

                 arg.buf指向的结构中.要求有读权限.

             . IPC_SET:设置semid相关数据结构的如下成员,设置数据从

              arg.buf指向的结构中读取:

                   sem_perm.uid

                sem_perm.gid

                sem_perm.mode

                 本命令只能由有效UID等于sem_perm.cuidsem_perm.uid

                 进程或有效UID有合适权限的进程操作.

             . IPC_RMID:删除由semid指定的信号量标识符和相关的一组信号

                 量及数据结构.本命令只能由有效UID等于sem_perm.cuid

                 sem_perm.uid的进程或有效UID有合适权限的进程操作.

        返回值:若调用成功,则根据cmd返回以下值:

                 GETVAL:semval的值.

               GETPID:sempid的值.

               GETNCNT:semncnt的值.

               GETZCNT:semzcnt的值.

               其他:0.

               若调用失败则返回-1.

 

阅读(1126) | 评论(0) | 转发(0) |
0

上一篇:希望在这能结交朋友,共同进步

下一篇:没有了

给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册