关于pthread与文件描述符

本文探讨了在创建新线程后,主线程如何共享文件描述符的问题。通过示例代码展示了即使新线程在主线程打开文件后,依然能通过共享的files_struct结构访问并读写文件。分析了在生成线程时,files_struct如何保持共享,而在生成进程时,files_struct会被复制。结论是pthread_create创建的线程与主线程共享files_struct,使得线程间能使用相同的文件句柄。

新创建线程和主线程会共享地址空间,但在实际执行中各自都有独立的task_struct。那么在pthread_create一个新线程之后,如果主线程再打开一个文件,原有线程是否可以用主线程的句柄读写文件呢。一般来说通过文件句柄来读写文件遵循下面的流程通过对sys_clone的分析应该是可以的,下面验证一下:

1.源码

#include<stdlib.h>
#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int iOpenFd = -1;
char *str = "Hello World";

void *func(void *arg)
{
  while(1)
  {
    sleep(2);
    if(iOpenFd < 0)
    {
      printf("thread says:openfd < 0\n");
      continue;
    }
    write(iOpenFd , str , strlen(str));
    printf("done\n");
  }
  return 0;
}



int main(int argc , char **argv)
{
  int ifd = -1;
  pthread_t tid;
  if(pthread_create(&tid , NULL , func , NULL) < 0)
  {
    printf("create thread failed!\n");
    return -1;
  }

  sleep(4);
  printf("try to open thread.info\n");

  ifd = open("./thread.info" , O_RDWR|O_APPEND);
  if(ifd < 0)
  {
    printf("open failed!\n");
    return -1;
  }
  printf("open success!\n");
  iOpenFd = ifd;
  pause();

  return 0;

}


iOpenFd再创建一个新线程之后在主线程打开,如果线程1共享了对应的打开文件信息,则通过检验iOpenFd则可以拿到对应的文件描述符进行读写。因为一般打开文件基于以下流程

fd-->task_struct.files_struct->fd[fd]-->files。所以就算iOpenFd>0如果线程1的task_struct.file_struct指向新的一块地址,那么task_struct.files_struct->fd[iOpenFd]==NULL也无法读写,所以如果能够正常读写则说明线程1的task_struct1.files_struct与主线程的task_struct0.files_struct指向同一块内存地址

2.执行命令 主线程pid=5718

3.查看主线程打开的文件

lsof -p 5718

COMMAND  PID    USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME

...

thread  5718 xx    0u   CHR  136,1      0t0       4 /dev/pts/1
thread  5718 xx   1u   CHR  136,1      0t0       4 /dev/pts/1
thread  5718 xx   2u   CHR  136,1      0t0       4 /dev/pts/1
thread  5718 xx    3u   REG 202,17      136 4784176 /data/home/xx/prog/env/env1/thread.info

4.查看线程1打开的文件,这个直接通过/proc/pid/fd来查看即可

ps -Lfp 5718
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
xx   5718  2324  5718  0    2 20:49 pts/1    00:00:00 ./thread

xx   5718  2324  5719  0    2 20:49 pts/1    00:00:00 ./thread

可以看到对应的pid是5719

我们查看/proc/5719/fd

[root@aa /proc/5719/fd]# ls
total 0
lrwx------ 1 xx xx 64 Jul  9 20:51 0 -> /dev/pts/1
lrwx------ 1 xx xx 64 Jul  9 20:51 1 -> /dev/pts/1
lrwx------ 1 xx xx 64 Jul  9 20:50 2 -> /dev/pts/1
lrwx------ 1 xx xx 64 Jul  9 20:51 3 -> /data/home/xx/prog/env/env1/thread.info

OK 符合预期


总结:

在生成线程和进程时,会有两种不同的效果

>生成线程时

main_thread.files_struct与child_thread.files_struct指向同一块地址

>生成进程时

main_process.files_struct与chile_process.files_struct会指向不同的地址,但是系统会让子进程的files_struct copy父进程的files_struct内容从而继承父进程开始打开的文件

通过阅读源码kernel2.6.x也可以知道 对应函数在copy_files里面

static int copy_files(unsigned long clone_flags, struct task_struct * tsk)

{

...

   struct files_struct *oldf, *newf;
    struct file **old_fds, **new_fds;
    int open_files, size, i, error = 0, expand;

    /*
     * A background process may not have any files ...
     */
    oldf = current->files;
    if (!oldf)
        goto out;


   //这里是pthread_create调用生成线程传入标记 不会新生成files_struct 只是会对应的结构增加一个引用计数

   //所以与主线程共享fiels_struct

    if (clone_flags & CLONE_FILES) {
        atomic_inc(&oldf->count);
        goto out;
    }


   --生成子进程往下走

    /*
     * Note: we may be using current for both targets (See exec.c)
     * This works because we cache current->files (old) as oldf. Don't
     * break this.
     */
    tsk->files = NULL;
    error = -ENOMEM;
    newf = kmem_cache_alloc(files_cachep, SLAB_KERNEL);
    if (!newf)
        goto out;

    atomic_set(&newf->count, 1);

    spin_lock_init(&newf->file_lock);
    newf->next_fd        = 0;
    newf->max_fds        = NR_OPEN_DEFAULT;
    newf->max_fdset        = __FD_SETSIZE;
    newf->close_on_exec = &newf->close_on_exec_init;
    newf->open_fds        = &newf->open_fds_init;
    newf->fd        = &newf->fd_array[0];

    spin_lock(&oldf->file_lock);

    open_files = count_open_files(oldf, oldf->max_fdset);
    expand = 0;

    /*
     * Check whether we need to allocate a larger fd array or fd set.
     * Note: we're not a clone task, so the open count won't  change.
     */
    if (open_files > newf->max_fdset) {
        newf->max_fdset = 0;
        expand = 1;
    }
    if (open_files > newf->max_fds) {
        newf->max_fds = 0;
        expand = 1;
    }

    /* if the old fdset gets grown now, we'll only copy up to "size" fds */
    if (expand) {
        spin_unlock(&oldf->file_lock);
        spin_lock(&newf->file_lock);
        error = expand_files(newf, open_files-1);
        spin_unlock(&newf->file_lock);
        if (error < 0)
            goto out_release;
        spin_lock(&oldf->file_lock);
    }

    old_fds = oldf->fd;
    new_fds = newf->fd;

    memcpy(newf->open_fds->fds_bits, oldf->open_fds->fds_bits, open_files/8);
    memcpy(newf->close_on_exec->fds_bits, oldf->close_on_exec->fds_bits, open_files/8);


   //各种拷贝对应的结构数据

    for (i = open_files; i != 0; i--) {
        struct file *f = *old_fds++;
        if (f) {
            get_file(f);
        } else {
            /*
             * The fd may be claimed in the fd bitmap but not yet
             * instantiated in the files array if a sibling thread
             * is partway through open().  So make sure that this
             * fd is available to the new process.
             */
            FD_CLR(open_files - i, newf->open_fds);
        }
        *new_fds++ = f;
    }

    spin_unlock(&oldf->file_lock);


   ....

   tsk->files = newf; //新的结构赋值

   ...

}

通过strace 我们刚才的程序可以看到传入的标记

strace ./thread 2>&1 | grep CLONE_FILES
clone(child_stack=0x7fe620f1cff0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7fe620f1d9d0, tls=0x7fe620f1d700, child_tidptr=0x7fe620f1d9d0) = 6434



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值