博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

信号集的内部实现 位图的实现

Posted on 2016-03-23 12:49  bw_0927  阅读(341)  评论(0)    收藏  举报

http://www.zyfforlinux.cc/2014/10/22/%E4%BF%A1%E5%8F%B7%E9%9B%86%E7%9A%84%E5%AE%9E%E7%8E%B0/

 

前言

在分析信号的时候,突然对sigset信号集这个新的数据类型以及相应的五个操作信号集的函数产生了兴趣,很想直到是怎么实现的,以便自己拿过来使用,于是乎我开始了探索之旅。
注:本文只解释POSIX C对于signal.h文件中的GUN C部分不涉及.
凡是包裹在#defien __USE_GNU #endif这里面的都不是我们分析的对象。

signal.h文件分析

1
2
3
4
在C语言中使用#include<signalh.>的话,可以表明signal.h就是再/usr/include/下,那么就来分析下/usr/include/signal.h吧
打开这个文件感觉一头雾水根本没办法分析,全都不认识,先找找关键词把,搜索了下sigset看看这家伙是什么东西。

typedef __sigset_t sigset_t;

原来是sigset_t啊,那么sigset_t是什么呢,再次搜索找不到了,看来是include其他文件引入的,那就sigemptyset,sigaddsetm,sigdelset,
sigfillset,sigismember这些函数把.再次搜索。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Clear all signals from SET.  */
extern int sigemptyset (sigset_t *__set) __THROW __nonnull ((1));

/* Set all signals in SET. */
extern int sigfillset (sigset_t *__set) __THROW __nonnull ((1));

/* Add SIGNO to SET. */
extern int sigaddset (sigset_t *__set, int __signo) __THROW __nonnull ((1));

/* Remove SIGNO from SET. */
extern int sigdelset (sigset_t *__set, int __signo) __THROW __nonnull ((1));

/* Return 1 if SIGNO is in SET, 0 if not. */
extern int sigismember (const sigset_t *__set, int __signo)
__THROW __nonnull ((1));

注:THROW主要是用来抛出异常的,nonnull((1))表示第一个参数不能为空,主要是为了编译器优化。(来自与gcc扩展语法)
再次失望,又都是通过include引入的,看来signal.h并不是我们真正要分析的东西啊,这个文件里面引入了很多头文件,我咋知道哪个是和sigset相关的呢,或许带有sigset字眼的就是的把。那就找找把,

1
#include <bits/sigset.h>

有点头绪了,那就找找bits/sigset.h在哪把,按照推理应该是在

1
2
3
/usr/include/bits/sigset.h
可惜这次错了,左找右找,终于找到了在
/usr/include/x86_64-linux-gnu/bits/下面

注意:我用的是ubuntu14.04 64位系统,我的bits/sigset.h就在这个位置,其他的系统我不太清楚,需要搜索找找看。

bits/sigset.h文件分析

1
2
打开这个文件看看
vim /usr/include/x86_64-linux-gnu/bits/sigset.h

首先映入眼帘的就是sigset的真身所在:

1
2
3
4
5
6
7
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;

#endif

原来sigset就是一个数组啊。但是这里存在一个问题我还没有弄懂,_SIGSET_NWORDS的大小问题,在我的机器上unsigned long int是8个字节,
那么经过计算_SIGSET_NWORDS就是16,也就是说sigset就是一个具有16个元素的数组喽,但是信号很多啊,难道只能屏蔽16种信号,这下蒙了。先不管这么多了,继续往后分析。看看五个操作信号集的函数是怎么实现的。

1
2
3
4
5
6
7
8
9
10
#  define __sigemptyset(set) \
(__extension__ ({ int __cnt = _SIGSET_NWORDS; \
sigset_t *__set = (set); \
while (--__cnt >= 0) __set->__val[__cnt] = 0; \
0; }))
# define __sigfillset(set) \
(__extension__ ({ int __cnt = _SIGSET_NWORDS; \
sigset_t *__set = (set); \
while (--__cnt >= 0) __set->__val[__cnt] = ~0UL; \
0; }))

注:extension这是啥,其实这是一个宏,gcc提供的,因为gcc在c语言的基础上提供了很多扩展,为了在编译的时候不发出警告可以使用extension
这是啥,一眼看上去根本看不懂,其实这五个函数都是宏定义实现的。上面的形式可能一眼看不出来。我改成函数的形式给大家看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void __sigemptyset(int *set)
{
//首先获取sigset的元素个数
int __cnt = _SIGSET_NWORDS;
//指向传递过来的sigset,对其元素进行修改
sigset_t * __set = set;
//遍历元素,赋值0
while(--__cnt>=0)__set->__val[__cnt] =0;

}


void __sigfillset(int *set)
{
int __cnt = _SIGSET_NWORDS;
sigset_t *__set = set;
//遍历元素赋值0UL代表的是无符号长整形的0
while(--__cnt>=0)__set->__val[__cnt] = ~0UL;
}

这回比较清楚了把,原来这两个函数的实现这么简单啊。
继续分析其他三个函数的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ifdef __USE_EXTERN_INLINES
# define __SIGSETFN(NAME, BODY, CONST) \
_EXTERN_INLINE int \
NAME (CONST __sigset_t *__set, int __sig) \
{ \
unsigned long int __mask = __sigmask (__sig); \
unsigned long int __word = __sigword (__sig); \
return BODY; \
}

__SIGSETFN (__sigismember, (__set->__val[__word] & __mask) ? 1 : 0, const)
__SIGSETFN (__sigaddset, ((__set->__val[__word] |= __mask), 0), )
__SIGSETFN (__sigdelset, ((__set->__val[__word] &= ~__mask), 0), )

# undef __SIGSETFN
# endif

找到了另外三个的三个函数的实现了,发现看不懂了。这是啥东东呢,还是宏。而且还是通过宏展开来实现函数的定义。仔细看看里面有个sigmask和sigwork不认识。找找看这是啥

1
2
3
4
5
# define __sigmask(sig) \
(((unsigned long int) 1) << (((sig) - 1) % (8 * sizeof (unsigned long int))))

/* Return the word index for SIG. */
# define __sigword(sig) (((sig) - 1) / (8 * sizeof (unsigned long int)))

两个宏定义,先来分析分析这两个宏把。
sigmmask的话,传入的sig应该是一个数字代表信号。 紧接着是对sig-1取64的模,因为信号总共就是64个信号. 所以对64取模就不难理解了,接下来对1左移(sig-1)%64位。unsigned long int 1的二进制形式是前面63个0最后一个1,这六十四位分别对应着64个信号,假设传入信号5那么就得让第五个位置上的0变成1,也就是需要左移4位,这就是为什么sig需要减1的缘故。基本上通过上面的描述以及对sigmask这个宏有了基本的认识。
接下来看看__sigword这个宏把,很简单就是(sig-1)/64,注意这里是/不是%
5/64就是0,65/64就是1。看到这里似乎有点明白了sigset_t是一个数组的原因了。
接着分析:sigismember这个函数:
写成函数的形式的话就是如下:

1
2
3
4
5
6
7
8
extren inline int __sigismembebr(const __sigset_t *__set,int __sig)
{
//产生信号屏蔽位
unsigned long int __mask = __sigmask(__sig);
//产生数组下标的索引
unsigned long int __word = __sigwoord(__sig);
return (__set->__val[__word] & __mask)?1:0;
}

就是上面这样的一个函数,很简单,就是把

1
__SIGSETFN (__sigismember, (__set->__val[__word] & __mask) ? 1 : 0, const)

里面的三个参数带入SIGSETFN宏展开就可以了。这个函数的目的显而易见。测试某一位是否是0如果是0返回0,如果是1返回1.
到了这里终于恍然大悟,原来之前看到的
sigset_t我们起初认为是一个有16个元素的的数组,其实不然,每个元素是一个unsigned long int具有64
位,这64位代表64个信号。其实只用到了1个元素。所以word代表数组下标的索引0,如果信号是65那么第1个元素只有64位放不下去了,就只能
放在第二个元素里面。所以
word是1。基本懂了。那么剩下的就是其他两个函数的实现了,也是比较简单的。变成函数的形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
extern inline int __sigaddset(__sigset_t *__set,int __sig)
{
//产生信号屏蔽位
unsigned long int __mask = __sigmask(__sig);
//产生数组下标的索引
unsigned long int __word = __sigwoord(__sig);
//添加信号集的过程成功的返回0
return ((__set->__val[__word] |= __mask),0)

}


extern inline int __sigdelset(__sigset_t *__set,int __sig)
{
//产生信号屏蔽位
unsigned long int __mask = __sigmask(__sig);
//产生数组下标的索引
unsigned long int __word = __sigwoord(__sig);
//删除信号集的过程成功返回0
return ((__set->__val[__word] &= ~__mask),0)

}

总结

分析信号集操作函数的过程受益匪浅,可以参考其代码的实现来编写属于自己的集合操作函数。