存档

‘Linux’ 分类的存档

Nginx使用instance标记处理kqueue/epoll中的stale事件

2015年8月28日 评论已被关闭

下面以epoll为例:

每个fd往往会关联一个自定义的结构体来表示逻辑上的连接。比如在nginx中每个ngx_connection_t都抽象成一个TCP连接并且与fd关联。

当我们accept到一个连接的时候,我们会申请这样一个ngx_connection_t,随后将这个fd与ngx_connection_t关联并加入到epoll循环。

代码(添加读事件)

然后我们使用epoll_wait来检查发生的事件

每个epoll_event的data.ptr是由ngx_connection_t*指针与instance相或而来,也就是data.ptr = c | instance;这样做是有前提条件的,那就是c必须是偶数,因为instance是bool类型,data.ptr最末位存储了instance的值。

 

假设当某一次epoll_wait后返回了一组epoll_event。

随后进行了如下操作:如图1所示:

(1)fd=47的文件描述有事件发生。

(2)处理fd=47时,我们关闭了fd=12的连接,回收了ngx_connection_t内存。

(3)接着处理fd=12时,此时data_ptr指向的ngx_connection_t已经被销毁,访问data_ptr将出现致命错误。

20150828160701

解决方法(如图2所示):暂不回收ngx_connection_t内存而是将ngx_connection_t的fd设置为-1,那么处理fd=12时,发现ngx_connection_t中的fd=-1,那么足以证明这个fd在之前已经被释放掉了,从面阻止使用data_ptr继续使用,这就是stale事件。

20150828160702

 

当我们将fd=12的ngx_connection_t回收后将fd设成了-1,防止处理到fd=12时使用了被释放的data_ptr。

试想一下,此时将fd设置成了-1,但在处理fd=12前,我将刚才释放的ngx_connection_t重新分配给了新连接fd=48(调用了accept()),那么在处理fd=12时,其data_ptr仍然不能使用,因这个ngx_connection_t已不属于fd=12而是属于fd=48,也就是说fd=12的ngx_connection_t已被释放并被fd=48使用。

我们使用instance来区分这个ngx_connection_t到底属于谁。因为本身data_ptr中存储了instance值,并且ngx_connection_t中也存储了instance值,在申请ngx_connection_t时,我们要将ngx_connection_t中的instance值置反。当ngx_connection_t的instance与data_ptr不一致时,说明当前fd已销毁。(如图3所示)

20150828160703

 

注意

另外,有一点要说明的是,如果在处理fd=12之前,fd=48也被销毁了,然后该ngx_connection_t又被fd=49复用,那么这种情况仍然会有问题。

这会导致data_ptr中的instance值与ngx_connection_t一致(两次置反)并且fd=49。event_list原本指示的fd=12有事件,但被nginx错误地认为了fd=49有事件发生,目前我还没想到好的解决办法。

分类: C/C++, Linux, Nginx 标签:

nginx中定时器与事件流程

2015年4月13日 评论已被关闭

1.nginx中的时间更新

Nginx在内部自己缓存时间,那么这些时间值在什么地方更新呢?

1.1 收到信号时更新时间

1.2 timer_resolution值为0

如果用户没有设置timer_resolution值或者设置为0值,那么每次在轮询事件都会设置NGX_UPDATE_TIME标志,表示轮询结束后要更新时间(以kqueue为例):

轮询时:

1.3 timer_resolution值非0

如果用户设置timer_resolution为非零值,nginx会在定时器触发时更新时间。

在nginx中系统定时器的具体实现有两种,一种是使用setitimer(),另外一种具体事件中模型实现的。

1.3.1使用setitimer()

在事件初始化后使用setitimer(timer_resolution),那么系统会每隔timer_resolution(单位为毫秒)发送SIGALARM信号,信号处理函数:

后续的的轮询会被打断并在结束后会去判断ngx_event_timer_alarm值,如果为1的话会更新时间(以kqueue为例):

1.3.2使用特定的事件模型

对于特定的事件模型,在这里只包括kqueue和eventport。它们内部实现了一个定时器,我们在事件初始化时安装定时器,例如(以kqueue为例):

同时设置还会置上NGX_USE_TIMER_EVENT标志,并且定时器触发时没有回调函数,我们要在事件轮询后判断是否是定时器事件,如果是则要更新时间(以kqueue为例):

 

总结:nginx自身缓存时间。 阅读全文…

分类: Linux, Nginx 标签:

rtmp直播流程

2014年6月24日 评论已被关闭

下面是通过RTMP抓包所得,左侧是客户端Flash Media Live Encoder,右侧是服务器Flash Media Server

 

分类: C/C++, Linux 标签:

Virtual Memory

2014年1月17日 评论已被关闭

虚拟内存一种抽象的形式,它使得每一个进程都以为自己独占整个主存。每一个进程的内存分布都有统一的视图,被称为它的虚拟地址空间。
Linux的虚拟地址空间如图1.13所示。(其它的Unix系统也使用类似的布局。)
在Linux中,操作系统把虚拟地址空间最顶端的区域(译者注:内核空间)保留用于所有进程的公共代码和数据。地址空间的低端区域(译者注:用户空间)被用户进程用来存储代码和数据。注意表格中的地址空间增长方向是从下到上的。
(译者注:在Linux系统中(32位),0x00000000~0xbfffffff为用户空间,0xc0000000~0xffffffff为内核空间。)
Process virtual address space.

每一个进程看到的虚拟地址空间是由一些被划分好的用于特定目的的区域组成。在本书的随后部分你会获取到更多关于这些区域的细节,但是简略地从低端开始向上看一下每个区域也是很有帮助的。

  • Program code and data.所有的进程的代码都从相同固定的地址开始,接下来是数据区域,相当于C语言中的全局变量。代码和数据是直接用可执行文件的内容进行初始化,在我们例子里是hello文件。在第7章我们学习链接和加载时会学习更多的关于地址空间的这部分内容。
  • Heap.在代码和数据区域之后紧跟着运行时的堆。代码和数据区域在程序开始运行一次性分配固定大小的区域,与它不同的是,堆的扩张和缩小是动态的,它是调用C标准库如malloc和free之后的运行结果。我们在第9章学习虚拟内存管理时将学习更多的关于堆的细节。
  • Shared libraries.大概在地址空间的中间部分的区域用于装载了共享库如C标准库和数学库的代码和数据。共享库的概念非常强大但比较难以理解。你将会在第7章学习动态链接时了解它们是如何工作的。
  • Stack.在用户虚拟内存地址空间的最顶端是用户栈,编译器利用它来实现函数调用。跟堆一样,在程序运行用户栈的扩张和缩小也是动态的。特别是我每们调用一次函数时,栈就是增长。每次函数返回时,栈就是缩小。你会在第3章学习到编译器如何使用栈。
  • Kernel virtual memory.内核是操作系统的一部分,它总是长驻于内存。地址空间的最顶端区保留给内核使用。应用程序不允许读写这部分区域和直接调用内核代码段里定义的函数。

正常运作虚拟内存需要硬件和操作系统软件一系列的复杂交互,包括对处理器使用的每一个地址进行硬件转换。基本的思想是将进程的虚拟内存的内容存储在磁盘上,把主存当作磁盘的缓存使用。第9章将会介绍它的工作原理并解释为什么它对于现代操作是如此的重要。

分类: Linux, 翻译 标签:

GBK与UTF-8之间的转化

2013年11月11日 没有评论

GBK编码与UTF-8之间的转化。
第一种是先将 GBK->Unicode->UTF-8;
第二种是通过iconv库,直接GBK->UTF-8;
Unicode编码包含所有字符集,而GBK(GB2312是GBK的一个子集),UTF-8等等都称为多字节集(Multibyte),两个多字节字符集之间的转换首先将源字符集映射到Unicode上,再将Unicode映射到目标多字节字符集上。有一个问题就是源字符集与目标字符集不可能是一一对应的,所以在转换的时候往往会有一个参数表示如果未找到对应的字符的处理情况。另外Unicode中的表示字符用wchar_t,而多字节的字符用char表示(也有多个char表示一个字符)。

windows下

C语言中有与WideCharToMultByte对应的接口是wctomb,MultiByteToWideChar对应的接口是mbtowc,但是对应的转化不能将正确的转换UTF-8。具体实现是先将当前语言为源字节集,将源字节集转化成Unicode,然后将语言设置成目标字节集将Unicode转化成目标字节集,具体参见:
setlocale: http://msdn.microsoft.com/en-us/library/aa272906(v=vs.60).aspx

利用第三方库实现,它就是大名鼎鼎的libiconv,有一个值得小心的地方,就是当目标字符集中没有源串中对的字符是可以选择继续还是忽略,所以及在open时会带上参数”//IGNORE”,要不然就遇到这种情况会停止转化。

分类: C/C++, Linux, Win32 标签:

使用原子操作实现自旋锁

2013年10月14日 没有评论

原子操作
在windows平台有InterlockedCompareExchange原子操作接口。实现对内存的互斥修改

如果对函数的解释有点疑惑,可以看一下该函数的大致实现。

自旋锁
如果我们所1代表“加锁”,0代表“未加锁”。加锁的过程是将内存值0改成1,所以我们可以利用这特性实现加锁操作和解锁操作。

知道trylock只是尝试去将0改成1,如果之前已经是1说明,锁被占用了。所以我们实现了自旋等待锁被释放。

更好的实现,用Sleep切换CPU。

对于基于linux的gcc也是有相应的“比较修改”接口__sync_bool_compare_and_swap(lock, old, set),它的参数与windows的接口有两点不一样,参数不一致,返回类型也不一样。它的trylock的实现:

example

分类: C/C++, Linux, Win32 标签:

哲学家就餐问题(C语言)

2013年5月2日 没有评论

哲学家就餐问题
有五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在桌上有五个碗和五只筷子,他们的生活方式是思考和进餐。平时,哲学家进行思考,饥饿时就便试图去他们左右的筷子来就餐。只有在他拿到两个筷子时才能就餐。

服务生解法
所有哲学家想吃饭都必须告诉服务生,吃完饭同时也告诉服务生。由服务生根据筷子的使用情况决定是否准予吃饭。
下载:waiter.c 

编译: gcc waiter.c -lpthread -o waiter
运行:./waiter

分类: C/C++, Linux 标签:

strncpy与strncmp

2012年9月17日 没有评论

strncmp与strcmp
二者都字符串比较,前者有限定最多只能比较n个字符
如:

  1. strcmp(“hello”,“hel”);    //none zero
  2. strncmp(“hello”,“hel”,3); //zero

strncmp与memcmpy
二者都限定最多比较n个字符,但前者如果遇到’\ 0’结束比较
如:

  1. strncmp(“ababc”,“abcba”,6); //zero
  2. memcmp(“ababc”,“abcba”,6);  //none zero

strncpy与strcpy
二者均为字符串复制,但前者限定最多只能复制n个字符(不会自动追加’\ 0’)
如:

  1. char b[]=“abcdef”;
  2. strcpy(b,“hello”);     //b: hello\ 0
  3. strncpy(b,“hello”,5);  //b: hellof
  4. strcpy(s,d)等价于strncpy(s,d,strlen(s)+1);

strncpy与memcpy
二者均为固定内存复制,但前者如果遇到\ 0会停止复制
如:

  1. char b[7]=“hello”;
  2. char d[7] = {0};
  3. strncpy(d,b,7); //d: “he\ 0\ 0\ 0\ 0\ 0”
  4. memcpy(d,b,7);  //d: “he\ 0llo\ 0”
分类: C/C++, Linux 标签:

Nginx源码分析(一)

2012年3月12日 没有评论

1.错误定义

分析源码从最简单的地方入手,首先来看nginx的错误码定义,代码在ngx_errno.h,ngx_errno.h文件中。一共就两个函数。

  1. u_char *ngx_strerror(ngx_err_t err, u_char *errstr, size_t size);
  2. ngx_uint_t ngx_strerror_init(void);

系统启动时会初始化全局变量ngx_sys_errlist,这是一个全局ngx_str_t数组,数组大小为NGX_SYS_NERR,每个元素都一个结构体为:

  1. typedef struct {
  2.     size_t      len;//data成员的大小
  3.     u_char     *data;//一个字符串
  4. } ngx_str_t;

初始化:ngx_uint_t ngx_strerror_init
所以初始化的函数是填充每一个元素,该元素的数组下标即为错误码,元素len成员是指元素data字符串的大小,data是该错误码(数组下标)的文字描述,通过函数strerror(err)来取得错误码的信息串。
获取错误码对应的信息串:ngx_strerror
在ngx_sys_errlist找到err对应位置的错误信息,如果错误码不在数组下标范围则返回未知错误,同时检查传入的长度,太小于将会截断错误信息串。

分类: Linux, Nginx 标签:

VirtualBox从U盘启动方法

2011年12月25日 1 条评论

#进入命令行

#获取磁盘信息(这个最关键)

#打开d:/diskdrive.html ,查看DeviceID栏,获取到你的U盘DeviceID,如\\.\PHYSICALDRIVE1

#为U盘创建启动文件

# 添加磁盘d:\UsbDisk.vmdk, F12启动虚拟机,选择这个磁盘即可磁盘

分类: C/C++, Linux, Win32, 翻译, 计算机网络, 转载 标签: