`
kenby
  • 浏览: 722370 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

关于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线程标准,子进程与父进程共享相同的线程群

下面的例子是创建一个线程(子进程共享了父进程虚存空间,没有自己独立的虚存空间不能称其为进程)。
父进程被挂起当子线程释放虚存资源后再继续执行。
#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的底层实现
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
#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, 各种锁(内存锁,文件锁), 定时器, 未决信号
  • 大小: 15.4 KB
  • 大小: 18.3 KB
  • 大小: 19.9 KB
分享到:
评论

相关推荐

    Linux进程、线程和调度(1)

    接下来,我们将详细探讨与Linux进程、线程和调度相关的知识点。 首先,Linux进程是系统分配资源的基本单位,具有独立的地址空间。进程生命周期包括就绪(ready)、运行(running)、睡眠(sleep)、停止(stopped)...

    操作系统课程设计-linux进程与线程的通讯

    在操作系统课程设计中,Linux进程与线程的通信是一个核心且复杂的主题。在这个项目中,学生将深入理解操作系统内核如何管理和协调不同进程和线程之间的数据交换,从而实现高效的任务执行。以下是对这个主题的详细...

    linux 进程和线程

    linux下的进程、线程

    \嵌入式linux开发教程之进程与线程--千锋培训

    嵌入式Linux开发教程深入探讨了进程与线程的概念及其在操作系统中的实现,这对于理解和开发高效、并发的嵌入式应用程序至关重要。以下是这些知识点的详细解释: **一、基础知识:线程和进程** 1. **进程**:是操作...

    Linux进程和线程的基本编程、通讯和例程1

    总的来说,Linux进程和线程的编程涉及到多个层面,从基础概念到高级应用,都需要深入理解和实践。学习过程中,可以通过阅读官方文档、博客文章和开源项目来积累经验。同时,掌握有效的调试技巧和工具,能够帮助我们...

    linux 多进程多线程编程

    Linux 多进程多线程编程是指在 Linux 操作系统下使用 C 语言进行多进程和多线程编程的技术。该技术可以大幅度提高程序的执行效率和响应速度,提高系统的并发能力和资源利用率。 1. 创建缺省线程 在 Linux 系统下,...

    linux进程和线程编程PPT学习教案.pptx

    Linux进程和线程编程是操作系统领域的重要组成部分,尤其在服务器端和嵌入式系统中扮演着核心角色。本文将深入探讨Linux中的进程和线程概念,以及如何进行编程。 首先,进程是操作系统中的基本执行单元,它是一个...

    Linux进程、线程和调度(2)

    Linux进程、线程和调度是操作系统领域中的重要概念,涉及到程序执行、资源管理和并发控制等核心问题。在Linux操作系统中,进程和线程是进行并发操作的基本单位,而调度则是操作系统控制进程和线程执行的方式。下面将...

    linux 进程 线程 fork 的深入思考

    ### Linux 下进程、线程与 fork 的深入理解 #### 题目背景及解析 本篇文章将基于一道经典的面试题目来探讨 Linux 下进程创建机制,特别是 `fork` 函数的工作原理。该题目不仅考验应试者对进程创建的理解,还涉及了...

    linuxio进程线程网络编程

    ### Linux进程与线程 #### 进程 在Linux系统中,进程是资源分配的基本单位,每个进程都有一个唯一的进程ID(PID)。进程具有独立的地址空间,能够执行一段程序,并拥有自己的系统资源,如内存、文件描述符等。进程...

    linux 进程线程小程序

    本教程“linux 进程线程小程序”将深入探讨这两个概念,以及如何利用信号量、管道和共享内存进行进程间的通信。 首先,让我们了解什么是进程。在Linux系统中,进程是程序的一次动态执行过程,它具有独立的内存空间...

    linux 进程间通信及多线程操作源码

    在Linux操作系统中,进程间通信(IPC,Inter-Process Communication)和多线程编程是实现高效并发处理的关键技术。本文将深入探讨这两种技术,并结合"全双工邮箱通讯"的源码实例,来阐述如何利用它们实现数据交换。 ...

    linux unix下多线程与进程

    在Linux和Unix操作系统中,进程和线程是两种基本的执行单元,它们是系统资源管理和任务调度的基础。本文将深入探讨这两个概念,以及它们在实际应用中的差异和交互。 一、进程 进程是操作系统中一个独立的执行实体,...

    在Windows下创建进程和线程的API

    Windows 下创建进程和线程的 API 在 Windows 操作系统中,创建进程和线程是非常重要的一步骤,对于开发者来说,掌握这方面的知识点是非常必要的。下面我们将详细介绍 Windows 下创建进程和线程的 API。 一、实验...

    实验二-Linux进程、线程及编程.doc

    本资源摘要信息涵盖了 Linux 进程、线程及编程的基础知识,包括进程的创建、管理和同步,线程的概念和使用,以及编程技术的应用。本摘要信息将为读者提供了一个系统的了解 Linux 进程、线程及编程的基础知识。 一、...

    基于linux的进程和线程控制

    在Linux操作系统中,进程和线程是系统执行的基本单元,它们是程序的动态表现形式。在本实训项目中,我们将探讨如何在Linux环境下通过编程实现生产者-消费者问题以及文件的读写操作,这两个主题是操作系统课程中的...

    linux-进程和线程

    - **Linux进程地址空间布局**:进程的地址空间通常分为多个区域,包括文本(代码)区、数据区、堆区和栈区。每个进程都有自己独立的地址空间,互不影响。 - **进程特征**:包括独立的内存空间、拥有资源、并发执行...

    linux进程的最大线程数 及最大进程数.zip

    linux进程的最大线程数 及最大进程数.zip

    Linux进程、线程和调度(3)

    在讲解Linux进程、线程和调度的时候,我们首先需要了解几个核心概念: 1. **进程和线程**:进程是程序的一次执行,拥有自己的地址空间、文件句柄、系统资源等。线程是进程中的一个执行单元,共享进程的大部分资源,...

Global site tag (gtag.js) - Google Analytics