无话不谈|离 Linux 内核有多远?,Java( 三 )


完整的段落如下(双引号扩起来的几个段落) , 有兴趣的同学可以详细阅读:
“fork传递至_do_fork的clone_flags参数是固定的 , 所以它只能用来创建进程 , 内核提供了另一个系统调用clone , clone最终也调用_do_fork实现 , 与fork不同的是用户可以根据需要确定clone_flags , 我们可以使用它创建线程 , 如下(不同平台下clone的参数可能不同):
SYSCALL_DEFINE5(clone,unsignedlong,clone_flags,unsignedlong,newsp,int__user*,parent_tidptr,int,tls_val,int__user*,child_tidptr){return_do_fork(clone_flags,newsp,0,parent_tidptr,child_tidptr);}Linux将线程当作轻量级进程 , 但线程的特性并不是由Linux随意决定的 , 应该尽量与其他操作系统兼容 , 为此它遵循POSIX标准对线程的要求 。 所以 , 要创建线程 , 传递给clone系统调用的参数也应该是基本固定的 。
创建线程的参数比较复杂 , 庆幸的是pthread(POSIXthread)为我们提供了函数 , 调用pthread_create即可 , 函数原型(用户空间)如下 。
intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);第一个参数thread是一个输出参数 , 线程创建成功后 , 线程的id存入其中 , 第二个参数用来定制新线程的属性 。 新线程创建成功会执行start_routine指向的函数 , 传递至该函数的参数就是arg 。
pthread_create究竟如何调用clone的呢 , 大致如下:
//来源:glibcconstintclone_flags=(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SYSVSEM|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID|0);__clone(&start_thread,stackaddr,clone_flags,pd,&pd->tid,tp,&pd->tid);clone_flags置位的标志较多 , 前几个标志表示线程与当前进程(有可能也是线程)共享资源 , CLONE_THREAD意味着新线程和当前进程并不是父子关系 。
clone系统调用最终也通过_do_fork实现 , 所以它与创建进程的fork的区别仅限于因参数不同而导致的差异 , 有以下两个疑问需要解释 。
首先 , vfork置位了CLONE_VM标志 , 导致新进程对局部变量的修改会影响当前进程 。 那么同样置位了CLONE_VM的clone , 也存在这个隐患吗?答案是没有 , 因为新线程指定了自己的用户栈 , 由stackaddr指定 。 copy_thread函数的sp参数就是stackaddr , childregs->sp=sp修改了新线程的pt_regs , 所以新线程在用户空间执行的时候 , 使用的栈与当前进程的不同 , 不会造成干扰 。 那为什么vfork不这么做 , 请参考vfork的设计意图 。
其次 , fork返回了两次 , clone也是一样 , 但它们都是返回到系统调用后开始执行 , pthread_create如何让新线程执行start_routine的?start_routine是由start_thread函数间接执行的 , 所以我们只需要清楚start_thread是如何被调用的 。 start_thread并没有传递给clone系统调用 , 所以它的调用与内核无关 , 答案就在__clone函数中 。
为了彻底明白新进程是如何使用它的用户栈和start_thread的调用过程 , 有必要分析__clone函数了 , 即使它是平台相关的 , 而且还是由汇编语言写的 。
/*i386*/ENTRY(__clone)movl$-EINVAL,%eaxmovlFUNC(%esp),%ecx/*noNULLfunctionpointers*/testl%ecx,%ecxjzSYSCALL_ERROR_LABELmovlSTACK(%esp),%ecx/*noNULLstackpointers*///1testl%ecx,%ecxjzSYSCALL_ERROR_LABELandl$0xfffffff0,%ecx/*对齐*///2subl$28,%ecxmovlARG(%esp),%eax/*nonegativeargumentcounts*/movl%eax,12(%ecx)movlFUNC(%esp),%eaxmovl%eax,8(%ecx)movl$0,4(%ecx)pushl%ebx//3pushl%esipushl%edimovlTLS+12(%esp),%esi//4movlPTID+12(%esp),%edxmovlFLAGS+12(%esp),%ebxmovlCTID+12(%esp),%edimovl$SYS_ify(clone),%eaxmovl%ebx,(%ecx)//5int$0x80//6popl%edi//7popl%esipopl%ebxtest%eax,%eax//8jlSYSCALL_ERROR_LABELjzL(thread_start)ret//9L(thread_start)://10movl%esi,%ebp/*terminatethestackframe*/testl$CLONE_VM,%edijeL(newpid)L(haspid):call*%ebx/*…*/以__clone(&start_thread,stackaddr,clone_flags,pd,&pd->tid,tp,&pd->tid)为例 ,


推荐阅读