1. 程式人生 > >Linux下pipe使用注意事項

Linux下pipe使用注意事項

Linux下的pipe使用非常廣泛, shell本身就大量用pipe來粘合生產者和消費者的. 我們的伺服器程式通常會用pipe來做執行緒間的ipc通訊. 由於unix下的任何東西都是檔案,只要是檔案,在讀取的時候,,就會設定last access time, 所以pipe也不例外., 但是這個時間對我們沒有意義 如果pipe使用的非常頻繁的時候會碰到由於設定訪問時間導致的效能問題. 這個開銷遠比pipe讀寫的本身開銷大. 相比檔案讀寫的開銷, atime微不足道,但是對pipe來講就不同了.
這個事情是上次和多隆同學在把玩他的網路框架的時候,無意發現的.


我們來分析下pipe的這部分程式碼:


//pipe.c:L349
static ssize_t
pipe_read(struct kiocb *iocb, const struct iovec *_iov,
               unsigned long nr_segs, loff_t pos)
{
...
   if (ret > 0)
        file_accessed(filp);
    return ret;
}
我們可以看到在pipe讀的時候要設定 file_accessed時間的,接著:


//fs.h:L1761
extern void touch_atime(struct vfsmount *mnt, struct dentry *dentry);
static inline void file_accessed(struct file *file)
{
        if (!(file->f_flags & O_NOATIME))
                touch_atime(file->f_path.mnt, file->f_path.dentry);
}
如果檔案沒設定 O_NOATIME就真正動手設定atime,接著:


//inode.c:L1493
void touch_atime(struct vfsmount *mnt, struct dentry *dentry)
{
        struct inode *inode = dentry->d_inode;
        struct timespec now;
 
        if (inode->i_flags & S_NOATIME)
                return;
        if (IS_NOATIME(inode))
                return;
        if ((inode->i_sb->s_flags & MS_NODIRATIME) && S_ISDIR(inode->i_mode))
                return;
 
        if (mnt->mnt_flags & MNT_NOATIME)
                return;
        if ((mnt->mnt_flags & MNT_NODIRATIME) && S_ISDIR(inode->i_mode))
                return;
 
        now = current_fs_time(inode->i_sb);
 
        if (!relatime_need_update(mnt, inode, now))
                return;
 
        if (timespec_equal(&inode->i_atime, &now))
                return;
 
        if (mnt_want_write(mnt))
                return;
 
        inode->i_atime = now;
        mark_inode_dirty_sync(inode);
        mnt_drop_write(mnt);
}
我們可以看出上面的流程還是比較複雜的,開銷也很大.
我們來演示下:


$ cat > pipe_test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/unistd.h>
 
static int fds[2];
static pthread_t rp;
 
static void *rp_entry(void *arg) {
  char c[1];
  while (1 == read(fds[0], c, 1)) {
    if (*c == 'Q') break;
  }
  fprintf(stderr, "pipe read ok\n");
  return NULL;
}
 
int main(int argc, char *argv[]) {
  long i, n;
  int rc;
  if (argc < 2) {
    fprintf(stderr, "usage: pipe_test NNNNNN\n");
    return -1;
  }
  n = atol(argv[1]);
  pipe(fds);
  //fcntl(fds[0], F_SETFL, O_NOATIME);
  pthread_create(&rp, NULL, rp_entry, NULL);
  fprintf(stderr, "pipe write %ld...", n);
  for (i = 0; i < n; i++) {
    write(fds[1], "A", 1);
  }
  write(fds[1], "Q", 1);
  fprintf(stderr, "ok\n");
  pthread_join(rp, NULL);
  close(fds[0]);
  close(fds[1]);
  return 0;
}
CTRL+D
$ gcc -D_GNU_SOURCE pipe_test.c -lpthread
$ sudo opcontrol --setup --vmlinux=/usr/lib/debug/lib/modules/2.6.18-164.el5/vmlinux
$ sudo opcontrol --init && sudo opcontrol --reset && sudo opcontrol --start
$ ./a.out 10000000
pipe write 10000000...ok
pipe read ok
$ sudo opcontrol --shutdown
$ opreport -l|less            
samples  %        app name                 symbol name
378654   92.7742  vmlinux                  .text.acpi_processor_idle
12978     3.1797  vmlinux                  current_fs_time
2530      0.6199  vmlinux                  thread_return
2345      0.5745  vmlinux                  touch_atime
2253      0.5520  vmlinux                  .text.acpi_safe_halt
1597      0.3913  vmlinux                  timespec_trunc
1368      0.3352  vmlinux                  file_update_time
1253      0.3070  vmlinux                  __mark_inode_dirty
901       0.2208  vmlinux                  pipe_writev
768       0.1882  vmlinux                  __mutex_lock_slowpath
763       0.1869  vmlinux                  try_to_wake_up
270       0.0662  vmlinux                  copy_user_generic_unrolled
254       0.0622  vmlinux                  acpi_set_register
254       0.0622  vmlinux                  system_call
233       0.0571  vmlinux                  pipe_readv
188       0.0461  vmlinux                  dnotify_parent
167       0.0409  vmlinux                  mutex_unlock
...
我們可以看到touch_atime的開銷很大,遠比pipe的讀寫大.
這次把這行註釋去掉: fcntl(fds[0], F_SETFL, O_NOATIME); 指示pipe在讀的時候不更新atime,看下效果:


view sourceprint?
$ opreport -l|less
samples  %        app name                 symbol name
599018   95.2466  vmlinux                  .text.acpi_processor_idle
4140      0.6583  vmlinux                  .text.acpi_safe_halt
3281      0.5217  vmlinux                  thread_return
2812      0.4471  vmlinux                  current_fs_time
2615      0.4158  vmlinux                  file_update_time
1790      0.2846  vmlinux                  __mutex_lock_slowpath
1657      0.2635  vmlinux                  timespec_trunc
1341      0.2132  vmlinux                  try_to_wake_up
1281      0.2037  vmlinux                  mutex_unlock
1080      0.1717  vmlinux                  mutex_lock
1001      0.1592  vmlinux                  pipe_readv
925       0.1471  vmlinux                  pipe_writev
這下看不到touch_atime了,開銷省了,對於高效能伺服器是很重要的.
小結: 細節很重要,記得開檔案open的時候設定O_NOATIME或者用fcntl搞定它.
祝玩得開心!