Linux进程和线程的实现以及查看

知道了Linux进程、线程、线程池和协程的由来,但进程和线程是怎么实现的呢?进程和线程如何查看呢?

先有个整体的概念:应用层调用封装的libc库函数进行系统调用syscall,进程和线程最终都会调用do_fork函数产生一个task_struct结构,schedule函数会通过时钟中断来触发调度,如下图。

进程和线程的创建
创建进程

1. Linux系统调用fork和vfork来创建进程,参考内核源码/linux-4.5.2/kernel/fork.c:

fork:

SYSCALL_DEFINE0(fork)

{

    return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);

}

vfork:

SYSCALL_DEFINE0(vfork)

{

    return _do_fork(CLONE_VFORK |  CLONE_VM | SIGCHLD, 0,0, NULL, NULL, 0);

}

由源代码可知fork和vfork都调用do_fork函数,仅传入的clone_flags参数不同见表如下。clone_flags重要参数做了解:CLONE_NEWNS表示在新的命名空间启动子进程();CLONE_VM子进程和父进程运行于相同的内核空间;CLONE_VFORK父进程被挂起直至子进程释放虚拟内存资源;CLONE_FS子进程与父进程共享相同的文件系统。

clone_flags参数

2. 重点看下进程创建的核心do_fork函数,do_fork函数的整个执行流程如图,比较关键的是调用copy_process函数,成功后创建子进程后就可以获取到pid。

do_fork函数执行流程

看完图有木有发现fork和vfork的一个区别:(如果是vfork父进程休眠则等待子进程将其唤醒)vfork场景景下父进程会先休眠,等唤醒子进程后,再唤醒父进程。这样做有什么好处呢?个人认为在vfork场景下,子进程被创建出来时,是和父进程共享地址空间的(可以进行验证),并且它是只读的,只有执行exec创建新的内存程序映象时才会拷贝父进程的数据创建新的地址空间,假如这个时候父进程还在运行,就有可能产生脏数据或者发生死锁。在还没完全让子进程运行起来的时候,让其父进程休息是个比较好的办法。

fork和vfork的区别

3. 接着重点看copy_process函数的工作流程如下图。

copy_process执行流程

主要参数说明如下:

1)copy_semundo(clone_flags, p):拷贝系统安全相关的数据给子进程,如果clone_flags设置了CLONE_SYSVSEM,则复制父进程的sysvsem.undo_list到子进程;否则子进程的tsk->sysvsem.undo_list为NULL。

2)copy_files(clone_flags, p):如果clone_flags设置了CLONE_FILES,则父子进程共享相同的文件句柄;否则将父进程文件句柄拷贝给子进程。

3)copy_fs(clone_flags, p):如果clone_flags设置了CLONE_FS,则父子进程共享相同的文件系统结构体对象;否则调用copy_fs_struct拷贝一份新的fs_struct结构体,但是指向的还是进程0创建出来的fs,并且文件系统资源是共享的。

4)copy_sighand(clone_flags, p):如果clone_flags设置了CLONE_SIGHAND,则增加父进程的sighand引用计数;否则(创建的必定是子进程)将父进程的sighand_struct复制到子进程中。

5)copy_signal(clone_flags, p):如果clone_flags设置了CLONE_THREAD(是线程),则增加父进程的sighand引用计数;否则(创建的必定是子进程)将父进程的sighand_struct复制到子进程中。

6)copy_mm(clone_flags, p)内存地址空间的拷贝:如果clone_flags设置了CLONE_VM,则将子进程的mm指针和active_mm指针都指向父进程的mm指针所指结构;否则将父进程的mm_struct结构复制到子进程中,然后修改当中属于子进程而有别于父进程的信息(如页目录)。

7)copy_io(clone_flags, p):如果clone_flags设置了CLONE_IO,则子进程的tsk->io_context为current->io_context;否则给子进程创建一份新的io_context。

8)copy_thread_tls(clone_flags,  stack_start, stack_size, p, tls)栈的分配,在创建子进程的过程中,子进程的内核栈空间是随进程同时分配的。

创建内核线程

Linux中,create_kthread来创建内核线程:

static void create_kthread(struct kthread_create_info * create)

{

    int pid;

    pid = kernel_thread(kthread, create,  CLONE_FS | CLONE_FILES | SIGCHLD);

}

kernel_thread也会和fork一样最终调用_do_fork函数,该函数的实现在/linux-4.5.2/kernel/fork.c文件中:

pid_t kernel_thread(int (* fn)(void *), void *arg, unsigned long flags)

{

    return _do_fork(flags|CLONE_VM| CLONE_UNTRACED, (unsigned long)fn, (unsigned long)arg, NULL, NULL, 0);

}

查看内核线程:ps -fax

查询结果显示在[]号中的进程为内核线程:

# ps -fax

PID TTY        STAT    TIME  COMMAND

    2    ?            S          0:34    [kthreadd]

    3    ?            S      1276:07  \_ [ksoftirqd/0]

创建线程:用户线程库pthread

在libc库函数中,pthread库用于创建用户线程,其代码在libc目录下的nptl中。

libc库考虑不同系统兼容性问题,里面有一堆条件编译信息,这里忽略了这些信息,简单地调用pthread库创建一个线程来测试:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <pthread.h>

void*  test_fn(void* arg)

{

    printf("hello pthread.\n");

    sleep(5);

    return((void  *)0);

}

int main(int argc, char * * argv)

{

    pthread_t id;

    int ret;

    ret = pthread_create(&id, NULL, test_fn,  NULL);

    if(ret ! = 0)

    {

        printf("create pthread error! \n");

        exit(1);

    }

    printf("in main process.\n");

    pthread_join(id, NULL);

return 0;

}

gcc命令生成可执行文件:gcc -g -lpthread -Wall -o test_pthread test_pthread.c

strace跟踪系统调用strace ./test_pthread.c

mmap(NULL,  8392704,  PROT_READ|PROT_WRITE,  MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7fb6ade8a000

        brk(0)= 0x93d000

        brk(0x95e000)= 0x95e000

mprotect(0x7fb6ade8a000, 4096, PROT_NONE)= 0

clone(child_stack=0x7fb6ae689ff0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7fb6ae68a9d0, tls=0x7fb6ae68a700,child_tidptr=0x7fb6ae68a9d0) = 6186

从上面strace产生的结果,可以看出pthread创建线程的流程如下:

1)mmap分配用户空间的栈大小。

2)mprotect设置内存页的保护区(大小为4KB),这个页面用于监测栈溢出,如果对这片内存有读写操作,那么将会触发一个SIGSEGV信号。

3)通过clone调用创建线程。

通过对pthread分析,也可以知道用户线程的堆栈可以通过mmap从用户空间自行分配。

用户线程可以自己指定用户栈,地址空间和父进程共享,内核线程则只有和内核共享的同一个栈,同一个地址空间。

参考资料:陈科的《Linux内核分析及应用》1.2 Linux进程和线程的实现
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容