Buffered I/O and non-buffered I/O

本文探讨了Linux系统中的I/O操作模型,包括标准库提供的buffered I/O与系统调用实现的non-buffered I/O。重点介绍了如何通过设置O_DIRECT标志实现Direct I/O,以绕过操作系统缓存直接访问设备。

 

实验需要对Flash Disk做无系统缓冲的I/O操作,顺便了解了一下Linux下的I/O.

 

Linux上的块设备的操作可以分为两类:

  • 第一类是使用C标准库中的fopen/fread/fwrite 系列的函数,我们可以称其为 buffered I/O。

具体的I/O path如下

Application<->Library Buffer<->Operation System Cache<->File System/Volume Manager<->Device

 

library buffer是标准库提供的用户空间的buffer,可以通过setvbuf改变其大小。

  • 第二类是使用Linux的系统调用的open/read/write 系列的函数,我们可以称其为 non-buffered I/O。

I/O Path

Application<-> Operation System Cache <->File System/Volume Manager<->Device

 

此外,我们可以通过设置open的O_DIRECT标志来实现Direct I/O(或者叫Raw I/O),即绕过OS Cache,直接读取Device ( that's what we want^o^ ), 等于将OS cache换成自己管理的cache。不过,Linus在邮件列表中建议不这么做,而是使用posix_fadvice, madvice。[2]中表明Direct I/O比buffered I/O的性能高很多。

在使用O_DIRECT的注意buffer的address必须是block alignment的(i.e. 初始地址必须是boundary), 可以用posix_memalign()函数分配内存以得到这样的buffer。至于为什么要这样,与实现的mmap有关,参见[5].

 

 

参考:

  1. Linux: Accessing Files With O_DIRECT http://kerneltrap.org/node/7563
  2. Andrea Arcangeli , O_DIRECT Whitepaper http://www.ukuug.org/events/linux2001/papers/html/AArcangeli-o_direct.html
  3. A Trip Down the Data Path: I/O and Performance http://articles.directorym.net/_A_Trip_Down_the_Data_Path_IO_and_Performance-a894569.html
  4. Operating Systems System Calls and I/O http://articles.directorym.net/Operating_Systems_System_Calls_and_IO-a894576.html
  5. Linux Device Drivers, 2nd Edition, Chapter 13 mmap and DMA http://www.xml.com/ldd/chapter/book/ch13.html
  6. http://topic.youkuaiyun.com/u/20080806/10/cdb1faa1-0146-4e96-8b12-26ba60acdbb5.html
  7. http://lists.alioth.debian.org/pipermail/parted-devel/2007-July/thread.html#1855
  8. Read系统调用剖析, http://www.ibm.com/developerworks/cn/linux/l-cn-

 另外推荐一篇不错的文章:http://www.ibm.com/developerworks/cn/linux/l-async/

EXPORT int jitter_buffer_get(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t desired_span, spx_int32_t *start_offset) { spx_uint32_t i; unsigned int j; spx_int16_t opt; if (start_offset != NULL) *start_offset = 0; /* Syncing on the first call */ if (jitter->reset_state) { int found = 0; /* Find the oldest packet */ spx_uint32_t oldest=0; for (i=0;i<jitter->buffer_size;i++) { if (jitter->packets[i].data && (!found || LT32(jitter->packets[i].timestamp,oldest))) { oldest = jitter->packets[i].timestamp; found = 1; } } if (found) { jitter->reset_state=0; jitter->pointer_timestamp = oldest; jitter->next_stop = oldest; } else { packet->timestamp = 0; packet->span = jitter->interp_requested; return JITTER_BUFFER_MISSING; } } jitter->last_returned_timestamp = jitter->pointer_timestamp; if (jitter->interp_requested != 0) { packet->timestamp = jitter->pointer_timestamp; packet->span = jitter->interp_requested; /* Increment the pointer because it got decremented in the delay update */ jitter->pointer_timestamp += jitter->interp_requested; packet->len = 0; /*fprintf (stderr, "Deferred interpolate\n");*/ jitter->interp_requested = 0; jitter->buffered = packet->span - desired_span; return JITTER_BUFFER_INSERTION; } /* Searching for the packet that fits best */ /* Search the buffer for a packet with the right timestamp and spanning the whole current chunk */ for (i=0;i<jitter->buffer_size;i++) { if (jitter->packets[i].data && jitter->packets[i].timestamp==jitter->pointer_timestamp && GE32(jitter->packets[i].timestamp+jitter->packets[i].span,jitter->pointer_timestamp+desired_span)) break; } /* If no match, try for an "older" packet that still spans (fully) the current chunk */ if (i==jitter->buffer_size) { for (i=0;i<jitter->buffer_size;i++) { if (jitter->packets[i].data && LE32(jitter->packets[i].timestamp, jitter->pointer_timestamp) && GE32(jitter->packets[i].timestamp+jitter->packets[i].span,jitter->pointer_timestamp+desired_span)) break; } } /* If still no match, try for an "older" packet that spans part of the current chunk */ if (i==jitter->buffer_size) { for (i=0;i<jitter->buffer_size;i++) { if (jitter->packets[i].data && LE32(jitter->packets[i].timestamp, jitter->pointer_timestamp) && GT32(jitter->packets[i].timestamp+jitter->packets[i].span,jitter->pointer_timestamp)) break; } } /* If still no match, try for earliest packet possible */ if (i==jitter->buffer_size) { int found = 0; spx_uint32_t best_time=0; int best_span=0; int besti=0; for (i=0;i<jitter->buffer_size;i++) { /* check if packet starts within current chunk */ if (jitter->packets[i].data && LT32(jitter->packets[i].timestamp,jitter->pointer_timestamp+desired_span) && GE32(jitter->packets[i].timestamp,jitter->pointer_timestamp)) { if (!found || LT32(jitter->packets[i].timestamp,best_time) || (jitter->packets[i].timestamp==best_time && GT32(jitter->packets[i].span,best_span))) { best_time = jitter->packets[i].timestamp; best_span = jitter->packets[i].span; besti = i; found = 1; } } } if (found) { i=besti; /*fprintf (stderr, "incomplete: %d %d %d %d\n", jitter->packets[i].timestamp, jitter->pointer_timestamp, chunk_size, jitter->packets[i].span);*/ } } /* If we find something */ if (i!=jitter->buffer_size) { spx_int32_t offset; /* We (obviously) haven't lost this packet */ jitter->lost_count = 0; /* In this case, 0 isn't as a valid timestamp */ if (jitter->arrival[i] != 0) { update_timings(jitter, ((spx_int32_t)jitter->packets[i].timestamp) - ((spx_int32_t)jitter->arrival[i]) - jitter->buffer_margin); } /* Copy packet */ if (jitter->destroy) { packet->data = jitter->packets[i].data; packet->len = jitter->packets[i].len; } else { if (jitter->packets[i].len > packet->len) { speex_warning_int("jitter_buffer_get(): packet too large to fit. Size is", jitter->packets[i].len); } else { packet->len = jitter->packets[i].len; } for (j=0;j<packet->len;j++) packet->data[j] = jitter->packets[i].data[j]; /* Remove packet */ speex_free(jitter->packets[i].data); } jitter->packets[i].data = NULL; /* Set timestamp and span (if requested) */ offset = (spx_int32_t)jitter->packets[i].timestamp-(spx_int32_t)jitter->pointer_timestamp; if (start_offset != NULL) *start_offset = offset; else if (offset != 0) speex_warning_int("jitter_buffer_get() discarding non-zero start_offset", offset); packet->timestamp = jitter->packets[i].timestamp; jitter->last_returned_timestamp = packet->timestamp; packet->span = jitter->packets[i].span; packet->sequence = jitter->packets[i].sequence; packet->user_data = jitter->packets[i].user_data; /* Point to the end of the current packet */ jitter->pointer_timestamp = jitter->packets[i].timestamp+jitter->packets[i].span; jitter->buffered = packet->span - desired_span; if (start_offset != NULL) jitter->buffered += *start_offset; return JITTER_BUFFER_OK; } /* If we haven't found anything worth returning */ /*fprintf (stderr, "not found\n");*/ jitter->lost_count++; /*fprintf (stderr, "m");*/ /*fprintf (stderr, "lost_count = %d\n", jitter->lost_count);*/ opt = compute_opt_delay(jitter); /* Should we force an increase in the buffer or just do normal interpolation? */ if (opt < 0) { /* Need to increase buffering */ /* Shift histogram to compensate */ shift_timings(jitter, -opt); packet->timestamp = jitter->pointer_timestamp; packet->span = -opt; /* Don't move the pointer_timestamp forward */ packet->len = 0; jitter->buffered = packet->span - desired_span; return JITTER_BUFFER_INSERTION; /*jitter->pointer_timestamp -= jitter->delay_step;*/ /*fprintf (stderr, "Forced to interpolate\n");*/ } else { /* Normal packet loss */ packet->timestamp = jitter->pointer_timestamp; desired_span = ROUND_DOWN(desired_span, jitter->concealment_size); packet->span = desired_span; jitter->pointer_timestamp += desired_span; packet->len = 0; jitter->buffered = packet->span - desired_span; return JITTER_BUFFER_MISSING; /*fprintf (stderr, "Normal loss\n");*/ } }
09-11
### 功能分析 `jitter_buffer_get` 函数的主要功能是从抖动缓冲区中获取合适的数据包。具体步骤如下: 1. **初始化与同步**:若 `start_offset` 不为空,将其初始化为 0。若抖动缓冲区处于重置状态,会寻找最旧的数据包,将 `pointer_timestamp` 和 `next_stop` 设置为该数据包的时间戳,并将重置状态标志置为 0。若未找到数据包,则返回 `JITTER_BUFFER_MISSING` [^1]。 ```c if (start_offset != NULL) *start_offset = 0; if (jitter->reset_state) { int found = 0; spx_uint32_t oldest = 0; for (i = 0; i < jitter->buffer_size; i++) { if (jitter->packets[i].data && (!found || LT32(jitter->packets[i].timestamp, oldest))) { oldest = jitter->packets[i].timestamp; found = 1; } } if (found) { jitter->reset_state = 0; jitter->pointer_timestamp = oldest; jitter->next_stop = oldest; } else { packet->timestamp = 0; packet->span = jitter->interp_requested; return JITTER_BUFFER_MISSING; } } ``` 2. **处理插值请求**:若存在插值请求,将当前指针时间戳和插值请求的跨度赋值给返回的数据包,指针时间戳增加插值请求的跨度,数据包长度置为 0,清除插值请求标志,并返回 `JITTER_BUFFER_INSERTION` [^1]。 ```c if (jitter->interp_requested != 0) { packet->timestamp = jitter->pointer_timestamp; packet->span = jitter->interp_requested; jitter->pointer_timestamp += jitter->interp_requested; packet->len = 0; jitter->interp_requested = 0; jitter->buffered = packet->span - desired_span; return JITTER_BUFFER_INSERTION; } ``` 3. **搜索合适的数据包**:按不同规则搜索合适的数据包,依次尝试寻找时间戳匹配且跨度足够的数据包、时间戳更早但跨度足够的数据包、时间戳更早且部分覆盖当前块的数据包,以及最早且跨度最大的数据包。若找到合适的数据包,更新相关时间信息,复制数据包内容,更新指针时间戳,并返回 `JITTER_BUFFER_OK` [^1]。 ```c // 搜索时间戳匹配且跨度足够的数据包 for (i = 0; i < jitter->buffer_size; i++) { if (jitter->packets[i].data && jitter->packets[i].timestamp == jitter->pointer_timestamp && GE32(jitter->packets[i].timestamp + jitter->packets[i].span, jitter->pointer_timestamp + desired_span)) break; } // 其他搜索逻辑... if (i != jitter->buffer_size) { spx_int32_t offset; jitter->lost_count = 0; if (jitter->arrival[i] != 0) { update_timings(jitter, ((spx_int32_t)jitter->packets[i].timestamp) - ((spx_int32_t)jitter->arrival[i]) - jitter->buffer_margin); } // 复制数据包 if (jitter->destroy) { packet->data = jitter->packets[i].data; packet->len = jitter->packets[i].len; } else { if (jitter->packets[i].len > packet->len) { speex_warning_int("jitter_buffer_get(): packet too large to fit. Size is", jitter->packets[i].len); } else { packet->len = jitter->packets[i].len; } for (j = 0; j < packet->len; j++) packet->data[j] = jitter->packets[i].data[j]; speex_free(jitter->packets[i].data); } jitter->packets[i].data = NULL; offset = (spx_int32_t)jitter->packets[i].timestamp - (spx_int32_t)jitter->pointer_timestamp; if (start_offset != NULL) *start_offset = offset; else if (offset != 0) speex_warning_int("jitter_buffer_get() discarding non - zero start_offset", offset); packet->timestamp = jitter->packets[i].timestamp; jitter->last_returned_timestamp = packet->timestamp; packet->span = jitter->packets[i].span; packet->sequence = jitter->packets[i].sequence; packet->user_data = jitter->packets[i].user_data; jitter->pointer_timestamp = jitter->packets[i].timestamp + jitter->packets[i].span; jitter->buffered = packet->span - desired_span; if (start_offset != NULL) jitter->buffered += *start_offset; return JITTER_BUFFER_OK; } ``` 4. **处理未找到数据包的情况**:若未找到合适的数据包,增加丢失计数,计算最优延迟。若最优延迟小于 0,进行插值操作,返回 `JITTER_BUFFER_INSERTION`;否则,进行正常丢包处理,返回 `JITTER_BUFFER_MISSING` [^1]。 ```c if (i == jitter->buffer_size) { jitter->lost_count++; opt = compute_opt_delay(jitter); if (opt < 0) { shift_timings(jitter, -opt); packet->timestamp = jitter->pointer_timestamp; packet->span = -opt; packet->len = 0; jitter->buffered = packet->span - desired_span; return JITTER_BUFFER_INSERTION; } else { desired_span = ROUND_DOWN(desired_span, jitter->concealment_size); packet->timestamp = jitter->pointer_timestamp; packet->span = desired_span; jitter->pointer_timestamp += desired_span; packet->len = 0; jitter->buffered = packet->span - desired_span; return JITTER_BUFFER_MISSING; } } ``` ### 性能优化 1. **减少不必要的遍历**:当前代码在搜索合适数据包时,进行了多次遍历。可以考虑使用更高效的数据结构,如哈希表或二叉搜索树,来存储数据包,以减少搜索时间。 2. **缓存中间结果**:对于一些频繁计算的结果,如 `compute_opt_delay` 函数的返回值,可以进行缓存,避免重复计算。 3. **并发处理**:如果系统支持并发操作,可以在更新时间信息、复制数据包等操作时采用并发处理,提高函数的执行效率。 ### 错误排查 1. **内存管理问题**:在复制数据包时,若 `jitter->packets[i].len > packet->len`,会输出警告信息,但仍可能导致数据丢失。应确保 `packet` 有足够的空间来存储数据包,或者动态调整 `packet` 的大小。 ```c if (jitter->packets[i].len > packet->len) { speex_warning_int("jitter_buffer_get(): packet too large to fit. Size is", jitter->packets[i].len); } else { packet->len = jitter->packets[i].len; } ``` 2. **时间戳计算问题**:在更新时间信息时,如 `update_timings` 函数的调用,要确保输入的参数计算正确,避免因时间戳计算错误导致的延迟或丢包问题。 ```c if (jitter->arrival[i] != 0) { update_timings(jitter, ((spx_int32_t)jitter->packets[i].timestamp) - ((spx_int32_t)jitter->arrival[i]) - jitter->buffer_margin); } ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值