进程和线程

发布时间:2016-12-8 12:15:08 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"进程和线程",主要涉及到进程和线程方面的内容,对于进程和线程感兴趣的同学可以参考一下。

关于Linux的进程和线程 博客分类:  linux   什么是进程 直观点说,保存在硬盘上的程序运行以后,会在内存空间里形成一个独立的内存体, 这个内存体有自己的地址空间,有自己的堆,上级挂靠单位是操作系统。操作系统 会以进程为单位,分配系统资源,所以我们也说,进程是资源分配的最小单位。 什么是线程 线程存在与进程当中,是操作系统调度执行的最小单位。说通俗点,线程就是干活的。 进程和线程的区别与联系 如果说进程是一个资源管家,负责从主人那里要资源的话,那么线程就是干活的苦力。 一个管家必须完成一项工作,就需要最少一个苦力,也就是说,一个进程最少包含一 个线程,也可以包含多个线程。苦力要干活,就需要依托于管家,所以说一个线程, 必须属于某一个进程。进程有自己的地址空间,线程使用进程的地址空间, 也就是说,进程里的资源,线程都是有权访问的,比如说堆啊,栈啊,静态存储区什么的。 线程就是个无产阶级,但无产阶级干活,总得有自己的劳动工具吧,这个劳动工具就是栈, 线程有自己的栈,这个栈仍然是使用进程的地址空间,只是这块空间被线程标记为了栈。 每个线程都会有自己私有的栈,这个栈是不可以被其他线程所访问的。 下面两副图展示了Linux下典型的进程环境和线程环境:                                    图1  Linux进程环境示意图                            图2  Linux线程环境示意图 一个牛叉的函数 -- 创建进程和线程的基础   Linux下进程和线程的创建都是通过clone实现的. clone函数功能强大,带了众多参数, clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和父进程 共享一个虚存空间,从而创造的是线程,你也可以不和父进程共享,你甚至可以选 择创造出来的进程和父进程不再是父子关系,而是 兄弟关系。先有必要说下这个函数的结构 int clone(int (*fn)(void *), void *child_stack, int flags, void *arg); 这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针, child_stack是为子 进程分配系统堆栈空间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中, 低地址上放入了值,这个值就是进程控制块task_struct的 值),flags就是标志用来描述你 需要从父进程继承那些资源, arg就是传给子进程的参数)。下面是flags可以取的值 标志                     含义 CLONE_PARENT  创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了兄弟而不是父子 CLONE_FS      子进程与父进程共享相同的文件系统,包括root、当前目录、umask CLONE_FILES   子进程与父进程共享相同的文件描述符(file descriptor)表 CLONE_NEWNS   在新的namespace启动子进程,namespace描述了进程的文件hierarchy CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表 CLONE_PTRACE  若父进程被trace,子进程也被trace CLONE_VFORK   父进程被挂起,直至子进程释放虚拟内存资源 CLONE_VM      子进程与父进程运行于相同的内存空间 CLONE_PID     子进程在创建时PID与父进程一致 CLONE_THREAD  Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群 下面的例子是创建一个线程(子进程共享了父进程虚存空间,没有自己独立的虚存空间不能称其为进程)。 父进程被挂起当子线程释放虚存资源后再继续执行。 C代码   #include <stdio.h>   #include <sched.h>   #include <signal.h>      #define FIBER_STACK 8192      int a;   void * stack;      int do_something()   {       printf("This is son, the pid is:%d,                the a is: %d\n", getpid(), ++a);       free(stack);        exit(1);   }      int main() {       void * stack;          a = 1;       //为子进程申请系统堆栈       stack = malloc(FIBER_STACK);       if(!stack) {           printf("The stack failed\n");           exit(0);       }          printf("creating son thread!!!\n");       clone(&do_something, (char *)stack + FIBER_STACK, CLONE_VM|CLONE_VFORK, 0);       printf("This is father, my pid is: %d, the a is: %d\n", getpid(), a);          exit(1);   }     关于fork, vfork,和clone的底层实现 C代码   asmlinkage int sys_vfork(struct pt_regs regs)   {       return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0);   }      asmlinkage int sys_fork(struct pt_regs regs)   {       return do_fork(SIGCHLD, regs.esp, ®s, 0);   }      asmlinkage int sys_clone(struct pt_regs regs)   {       unsigned long clone_flags;       unsigned long newsp;          clone_flags = regs.ebx;       newsp = regs.ecx;       if (!newsp)           newsp = regs.esp;       return do_fork(clone_flags, newsp, ®s, 0);   }     这里可以看到它们都是对do_fork的调用,不过是参数不同而已.其中最重要的是 clone_flags 参数, clone_flags由2部分组成,最低字节为信号类型,用于规定子进程去世时向父进程发出的信号。 fork和vfork中这个信号就是SIGCHLD,而clone则可以由用户自己定义。高字节部分表示子进程 从父进程继承哪些资源,fork的clone_flags参数的第2部分全部是0表示对所有资源都要复制给 子进程。而vfork的参数是 (CLONE_VFORK | CLONE_VM), 表示子进程和父进程共享虚存内存 CLONE_VFORK让父进程挂起, 直到子进程退出,至于clone则是由用户自己来定义的. pthread_create的实现 pthread_create是基于clone实现的, 创建出来的其实是进程, 但这些进程与父进程共享很多东西,  共享的东西都不用复制给子进程, 从而节省很多开销, 因此,这些子进程也叫轻量级进程(light-weight process) 简称LWP. 每个LWP都会与一个内核线程绑定, 这样它就可以作为独立的调度实体参与cpu竞争. 如下图所示: LWP被pthread封装后, 以线程面目示人, 它有自己的id, 这里要区分 phtread_create 给LWP分配的id, 和操作系统给LWP分配的进程id. 这两者是不同的, 前者用于标志线程,可以暴露给 用户, 后者其实就是进程的id, 它没有暴露出来, 必须通过系统调用来得到. 下面的例子演示了如何获取LWP的进程id 现出LWP的进程id cat lwp.c C代码   #include <unistd.h>   #include <stdio.h>   #include <string.h>   #include <pthread.h>   #include <stdlib.h>   #include <linux/unistd.h>      #define N 5      pid_t gettid()   {       //直接使用224代替__NR_gettid也可以       return syscall(__NR_gettid);   }      void *thread_func(void *arg)   {       printf("thread started, pid = %d\n", gettid());       while (1) {           sleep(1);       }   }      int main(void)        {            int i;       pthread_t tid[N];          for (i = 0; i < N; i++) {           pthread_create(&tid[i], NULL, thread_func, NULL);       }          sleep(1);       for (i = 0; i < N; i++) {           printf("tid = %lu\n", tid[i]);       }          while (1) {           sleep(1);       }       return 0;   }      work> gcc lwp.c -o lwp -lpthread work> ./lwp  thread started, pid = 12248  thread started, pid = 12249 thread started, pid = 12250  thread started, pid = 12251  thread started, pid = 12252  tid = 3078187888 tid = 3069795184 tid = 3061402480 tid = 3053009776 tid = 3044617072 ps ax 12247 pts/1    Sl+    0:00 ./lwp 12259 pts/3    R+     0:00 ps ax work> ps -Lf 12247 UID       PID   PPID  LWP   C   NLWP STIME TTY    STAT   TIME  CMD kenby    12247  3758 12247  0    6 20:22 pts/1    Sl+    0:00 ./lwp kenby    12247  3758 12248  0    6 20:22 pts/1    Sl+    0:00 ./lwp kenby    12247  3758 12249  0    6 20:22 pts/1    Sl+    0:00 ./lwp kenby    12247  3758 12250  0    6 20:22 pts/1    Sl+    0:00 ./lwp kenby    12247  3758 12251  0    6 20:22 pts/1    Sl+    0:00 ./lwp kenby    12247  3758 12252  0    6 20:22 pts/1    Sl+    0:00 ./lwp 此程序创建了5个线程, 但有6个LWP, 因为主线程也包含在里面, 主线程的线程id和进程id 相同, 然后这6个线程的进程id都相同, 因为他们属于同一个进程. 关于内核态和用户态 核心态可以执行特权指令,但用户态只能执行非特权指令.这是如何实现的呢? Linux将内核程序和用户程序分开处理,分别运行在用户态和核心态。 以32位x86架构为例,虚拟空间共4G,高地址的1G为系统程序运行的核心栈, 低地址的3G空间为用户程序运行的用户栈。Linux进程的4GB地址空间, 3G-4G部分大家是共享的,是内核态的地址空间,这里存放在整个内核的代码和所有 的内核模块,以及内核所维护的数据。用户运行一个程序,该程序所创建的进程开始是 运行在用户态的,如果要执行文件操作,网络数据发送等操作,必须通过write,send 等系统调用,这些系统调用会调用内核中的代码来完成操作,这时,必须切换到Ring0, 然后进入3GB-4GB中的内核地址空间去执行这些代码完成操作,完成后,切换回Ring3, 回到用户态。这样,用户态的程序就不能 随意操作内核地址空间,具有一定的安全保护作用。  关于内核线程 ps命令列出来的线程, 如果被"[]"括起来了, 这就是内核线程 再论fork fork后, 子进程复制哪些东西 一句话总结, 就是所有 writeable 的东西都会复制.包括: 堆,栈, 数据段, 未初始化数据段, 打开的文件描述符, 信号安装过的handler, 共享库, ipc(共享内存,消息队列,信号量) 注意未决信号不会继承过来, 新进程会重置它的未决信号链 子进程和父进程共享哪些东西 一句话总结, 就是所有 read-only 的东西都不用复制, 父子进程共享, 包括: 正文段和字符串常量 哪些东西, 子进程不会从父进程继承 进程id, 各种锁(内存锁,文件锁), 定时器, 未决信号

上一篇:Android UI 之WaterFall瀑布流效果
下一篇:linux ipc

相关文章

关键词: 进程和线程

相关评论