linux取消线程的原理,浅析 Linux 进程与线程

简介

进程与线程是所有的程序员都熟知的概念,简单来说进程是一个执行中的程序,而线程是进程中的一条执行路径。进程是操作系统中基本的抽象概念,本文介绍 Linux 中进程和线程的用法以及原理,包括创建、消亡等。

进程

创建与执行

Linux 中进程的创建与执行分为两个函数,分别是 fork 和 exec,如下代码所示:

int main() {

pid_t pid;

if ((pid = fork() < 0) {

printf("fork error\n");

} else if (pid == 0) {

// child

if (execle("/home/work/bin/test1", "test1", NULL) < 0) {

printf("exec error\n");

}

}

// parent

if (waitpid(pid, NULL) < 0) {

printf("wait error\n");

}

}

fork 从当前进程创建一个子进程,此函数返回两次,对于父进程而言,返回的是子进程的进程号,对于子进程而言返回 0。子进程是父进程的副本,拥有与父进程一样的数据空间、堆和栈的副本,并且共享代码段。

由于子进程通常是为了调用 exec 装载其它程序执行,所以 Linux 采用了写时拷贝技术,即数据段、堆和栈的副本并不会在 fork 之后就真的拷贝,只是将这些内存区域的访问权限变为只读,如果父子进程中有任一个要修改这些区域,才会修改对应的内存页生成新的副本,这样子是为了提高性能。

fork 之后父进程先执行还是子进程先执行是不确定的,所以如果要求父子进程进行同步,往往需要使用进程间通信。fork 之后子进程会继承父进程的很多东西,如:

打开的文件

实际用户 ID、组用户 ID 等

进程组

当前工作目录

信号屏蔽和安排

...

父子进程的区别在于:

进程 ID 不同

子进程不继承父进程的文件锁

子进程的未处理信号集为空

...

fork 之后,子进程可以执行不同的代码段,也可以使用 exec 函数执行其它的程序。

进程描述符

进程在运行的时候,除了加载程序,还会打开文件、占用一些资源,并且会进入睡眠等其它状态。操作系统为了支持进程的运行,必然有一个数据结构保存着这些东西。在 Linux 中,一个名为 task_struct 的结构保存了进程运行时的所有信息,称为进程描述符:

struct task_struct {

unsigned long state;

int prio;

pid_t pid;

...

}

进程描述符完整描述了一个进程:打开的文件、进程的地址空间、挂起的信号以及进程的信号等。系统将所有的进程描述符放在一个双端循环列表中:

进程描述符具体存放在内存的哪里呢?在内核栈的末尾。众所周知,进程中占用的内存一部分是栈,主要用于函数调用,不过这里说的栈一般指的是用户空间的栈,其实进程还有内核栈。当进程调用系统调用的时候,进程陷入内核,此时内核代表进程执行某个操作,此时使用的是内核空间的栈。

进程状态

进程描述符中的 state 描述了进程当前的状态,有如下 5 种:

TASK_RUNNING:进程是可执行的,此时进程要么是正在执行,要么是在运行队列中等待被调度

TASK_INTERRUPTIBLE:进程正在睡眠(阻塞),等待条件达成。如果条件达成或者收到信号,进程会被唤醒并且进入可运行状态

TASK_UNINTERRUPTIBLE:进程处于不可中断状态,就算信号也无法唤醒,这种状态用的比较少

_TASK_TRACED:进程正在被其它进程追踪,通常是为了调试

_TASK_STOPPED:进程停止运行,通常是接收到 SIGINT、SIGTSTP 信号的时候。

fork 与 vfork

在使用了写时拷贝后,fork 的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。fork 为了创建一个进程到底做了什么呢?fork 其实调用了 clone,这是一个系统调用,通过给 clone 传递参数,表明父子进程需要共享的资源,clone 内部会调用 do_fork,而 do_fork 的主要逻辑在 copy_process 中,大致有以下几步:

为新进程创建一个内核栈以及 task_struct,此时它们的值与父进程相同

将 task_struct 中某些变量,如统计信息,设置为 0

将子进程状态设置为 TASK_UNINTERRUPTIBLE,保证它不会被投入运行

分配 pid

根据传递给 clone 的参数,拷贝或者共享打开的文件、文件系统信息、信号处理函数以及进程的地址空间等。

返回指向子进程的指针

除了 fork 之外,Linux 还有一个类似的函数 vfork。它的功能与 vfork 相同,子进程在父进程的地址空间运行。不过,父进程会阻塞,直到子进程退出或者执行 exec。需要注意的是,子进程不能向地址空间写入数据。如果子进程修改数据、进行函数调用或者没有调用 exec 那么会带来未知的结果。vfork 在 fork 没有写时拷贝的技术时是有着性能优势,现在已经没有太大的意义。

退出

进程的运行终有退出的时候,有 8 种方式使进程终止,其中 5 中为正常终止:

从 main 返回

调用 exit

调用 _exit 或 _Exit

最后一个线程从其启动例程返回

从最后一个线程调用 pthread_exit

异常终止方式有 3 种:

调用 abort

接收到一个信号

最后一个线程对取消请求作出响应

exit 函数会执行标准 I/O 库的清理关闭操作:对所有打开的流调用 fclose 函数,所有缓冲中的数据会被冲洗,而 _exit 会直接陷入内核。看下面的代码:

#include

#include

#include

int main()

{

printf("line 1\n");

printf("line 2"); // 没有换行符

// exit(0)

_exit(0);

}

其中第二行输出没有 \n,如果末尾调用的是 _exit,则只会输出 line 1,如果替换为 exit,则第二行 line 2 也会输出。

进程退出最终会执行到系统的 do_exit 函数,主要有以下步骤:

删除进程定时器

释放进程占用的页表

递减文件描述符的引用计数,如果某个引用计数为 0,则关闭文件

向父进程发信号,给子进程重新找养父,并且把进程状态设置为 EXIT_ZOMBIE

调度其它进程

此时,进程的大部分资源都被释放了,并且不会进入运行状态。不过还有些资源保持着,主要是 task_struct 结构。之所以要留着是给父进程提供信息,让父进程知道子进程的一些信息,如退出码等。

需要注意的是,如果父进程不进行任何操作,那么这些信息会一直保留在内存中,成为僵尸进程,占用系统资源,如下面的代码:

int main() {

pid_t pid = fork();

if (pid == 0) {

exit(0);

} else {

sleep(10);

}

}

父进程 fork 出子进程后,子进程立刻退出,而父进程则进入睡眠。运行程序,观察进程状态:

可以看到,第一行进程为父进程,状态为 S,表示其正在睡眠,而第二为子进程,状态为 Z,表示僵尸状态(zombie),因为此时子进程已经退出,然而 task_struct 还保存着,等待父进程来处理。

父进程如何处理?调用 wait 函数,正如本文第一段代码中所示。当父进程调用 wait 后,子进程的 task_struct 才被释放。

如果父进程先结束了呢?在父进程结束的时候,会为其子进程找新的父进程,一直往上找,最终成为 init 进程的子进程。init 子进程会负责调用 wait 释放子进程的遗留信息。

线程

上面介绍了 Linux 中的进程,那么线程又是怎么的?网上一些说法是,Linux 中并没有真正的内核线程,线程是以进程的方式实现的,只不过它们之间会共享内存。这种说法有一定道理,但并不完全准确。

Linux 中刚开始是不支持线程的,后来出现了线程库 LinuxThreads,不过它有很多问题,主要是与 POXIS 标准不兼容。自 Linux 2.6 以来,Linux 中使用的就是新的线程库,NPTL(Native POSIX Thread Library)。

NPTL 中线程的创建也是通过 clone 实现的,并且通过以下的参数表明了线程的特征:

CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS |

CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM

部分参数的含义如下:

CLONE_VM:所有线程都共享同一个进程地址空间

CLONE_FILES:所有线程都共享进程的文件描述符列表

CLONE_THREAD:所有线程都共享同一个进程 ID 以及 父进程 ID

NPTL 所实现的线程库是 1:1 的从用户线程映射到内核线程,并且内核为了实现 POSIX 的线程标准也做了一些改动,比如对于信号的处理等。所以说 Linux 内核完全不区分进程和线程,甚至不知道线程的存在这种说法现在是不准确的。

线程间共享代码段、堆以及打开的文件等,线程私有的部分有以下内容:

线程 ID

寄存器

错误码(errno)

信号屏蔽

...

总结

Linux 中进程与线程的使用是程序员必备的技能,而如果能了解一些实现的原理,则可以使用的更加得心应手。本文介绍了 Linux 中进程的创建、执行以及消亡等,对于线程的实现及其与进程的关系也进行了简单的说明。进程和线程还有更多的内容可以研究,如进程调度、进程以及线程间的通信等。

参考

《UNIX 环境高级编程》

《Linux 内核设计与实现》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/508075.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux查看注册表信息,linux下登录档及其查看方法

一、Linux 常见的登录档档名登录档可以帮助我们瞭解很多系统重要的事件&#xff0c;包括登入者的部分资讯&#xff0c;因此登录档的权限通常是设定为仅有 root 能够读取而已。 而由于登录档可以记载系统这麽多的详细资讯&#xff0c;所以啦&#xff0c;一个有经验的主机管理员会…

Linux系统openssl升级,在Linux系统上升级OpenSSL的方法

我是用的centos &#xff0c;目前官方说受威胁的版本是1.0.1f, 1.0.1e, 1.0.1d, 1.0.1c, 1.0.1b, 1.0.1a, 1.0.1 。在openssl 1.0.1g版本中“ heartbleed”漏洞被修复。所有centos6.5的系统运行openssl 1.0.1e (openssl-1.0.1e-16.el6_5.4) 都会受到威胁&#xff0c;貌似只有6.…

linux系统建立文件系统,linux文件系统的建立

1根文件系统嵌入式Linux中都需要构建根文件系统&#xff0c;构建根文件系统的规则在FHS(Filesystem HierarchyStandard)文档中&#xff0c;下面是根文件系统顶层目录。目录内容bin存放所有用户都可以使用的、基本的命令。sbin存放的是基本的系统命令&#xff0c;它们用于启动系…

linux防火墙配置管理,Linux之Iptables防火墙管理与配置 -电脑资料

基本语法格式&#xff1a;iptables [ –t 表名 ] 命令选项 [ 链名 ] [ 条件匹配 ] [ –j 目标动作或跳转 ]Tip&#xff1a;若不指定表名&#xff0c;默认使用filter表&#xff0c;常用选项&#xff1a;-A&#xff1a;在指定链的末尾添加( –append )一条新规则。-D&#xff1a;…

linux双屏播放视频,Ubuntu Linux下双屏显示解决方案

Ubuntu从起后居然把Windows的设置给记住了。显示ok。我晕。以前倒是也有过这问题&#xff0c;在双系统情况下&#xff0c;外接键盘的灯会继承Windows的早就有心弄个显示器&#xff0c;把笔记本的外接上&#xff0c;倒不是说非要2个屏幕来工作如何如何&#xff1f;只是因为我的本…

d630 无线驱动 linux,fedora 8下DELL D630无线网卡驱动的安装

fedora 8下DELL D630无线网卡驱动的安装发布时间:2007-11-19 01:53:26来源:红联作者:Cantonese作者是dypang我的电脑是 DELL D630&#xff0c;无线网卡型号为 DELL wireless 1390 mini card。F8在 D630 上安装是很容易的&#xff0c;只是无线网卡的按照颇费了些周折。以下是我装…

鸟哥linux群,【鸟哥的linux私房菜-学习笔记】linux的帐号与群组

linux的帐号与群组使用者标识符&#xff1a; UID 与 GIDID 与账号的对应就在 /etc/passwd 当中&#xff1b;计算机只认得ID(即数字)&#xff0c;并不能区别账号&#xff1b;每个登陆的使用者至少都会取得两个 ID &#xff0c;一个是使用者 ID (User ID &#xff0c;简称 UID)、…

linux网络编程 华清,Linux网络编程之套接字

一 &#xff1a;套接字属性套接字由域(domain),类型(type)和协议(protocol)三个属性确定其特性。1)套接字的域域指定套接字通信中使用的网络 介质&#xff0c;常见的套接字域是AF&#xff3f;INET&#xff0c;它指的是互联网络&#xff0c;许多LINUX局域网使用的都是该网络。服…

用数据结构c语言写成绩排序,C语言数据结构 快速排序实例详解

C语言数据结构 快速排序实例详解一、快速排序简介快速排序采用分治的思想&#xff0c;第一趟先将一串数字分为两部分&#xff0c;第一部分的数值都比第二部分要小&#xff0c;然后按照这种方法&#xff0c;依次对两边的数据进行排序。二、代码实现#include /* 将两个数据交换 *…

c语言进制转换pdf下载,C语言 十六进制和十进制间的转换.pdf

1.将十六进制转换为十进制.#include#includeint main(void){int convert(int,char *);int i,j ;char m[20];printf("请输入你要转换的数:");scanf("%s",m);i 0;while(*(mi)! \0){i;}j convert(--i,m);printf("转换为十进制是:%d\n",j );return 0…

c语言 为什么i%3cn 1,c语言中n+1个基础且容易出错的知识点

前言为什么称为是n1呢&#xff0c;因为我会持续更新本条博客。1.“/”与“%”基本用法除法运算符“/” &#xff1a;二元运算符&#xff0c;具有左结合性。参与运算的量均为整形时&#xff0c;结果为整型&#xff0c;舍去小数。如果运算量中有一个实型&#xff0c;结果为双精度…

第二次上机报告c语言,第二次C语言设计上机报告

任务一&#xff1a;显示美元与人民币的汇率。实验内容&#xff1a;编写一个“美元与人民币汇率”的C程序。实验目的&#xff1a;进一步掌握C程序的编写和运行的结果。我的程序//***********************************#includeint main(){int dollar,RMB;int lower,upper,step;lo…

c语言使用循环编写勾股数,刘徽《九章算术》中的勾股数

若A、B、C为满足A2B2C2的正整数。我国古代数学书《周髀算经》曾经提到“勾广三&#xff0c;股修四&#xff0c;径偶五”这三个边都是正整数的直角三角形。在公元263年时&#xff0c;我国数学家&#xff1a;刘徽写了一本数学书&#xff0c;书名叫作《九章算术》&#xff0c;其中…

c语言边序列构造邻接表,结构C语言版期末考试考试(有答案).doc

人生难得几回搏&#xff0c;此时不搏更待何时&#xff1f;"数据结构"期末考试试题一、单选题(每小题2分共12分)1&#xff0e;在一个单链表HL中若要向表头插入一个由指针p指向的结点则执行( )A&#xff0e; HL&#xff1d;ps p一>next&#xff1d;HLB&#xff0e; …

c语言打不开h文件,说那个“mem.h”头文件打不开 怎么改啊 高手们帮帮忙

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼#include "stdio.h"#include "stdlib.h"#include "string.h"#include "conio.h"#include "mem.h"#include "ctype.h"#include "alloc.h"struct score{char…

C语言中表示温度符号,摄氏度符号怎么打(SCI论文中摄氏度°C符号的正确输法)...

大家可能知道中文的摄氏度百思特网是一个字符&#xff0c;而且输入方法比较简单&#xff0c;可以用搜狗输入法&#xff0c;还可以输入 "sheshidu"&#xff0c;第五个一般就是℃&#xff0c;甚至可以在网上或文献中复制粘贴。而英文的摄氏度C 却是两个字符&#xff0c…

利用spi发送接收信息c语言,SPI接收发送函数程序

unsigned char SPI_RW(unsigned char byte){?? ?unsigned char bit_ctr;?? ?for(bit_ctr0;bit_ctr<8;bit_ctr)?? ?{?? ??? ?NRF_MOSI(byte&0x80); // MSB TO MOSI?? ??? ?byte(byte<<1);?? ?// shift next bit to MSB?? ??? ?NRF_S…

android 跳转权限管理的代码,Android权限管理

Android权限管理说明在targetSdkVersion的值为23或者更高&#xff0c;就要进行权限管理&#xff0c;否则如果运行在Android6.0或以上的设备会没有相应权限而导致崩溃请求权限后&#xff0c;在onRequestPermissionsResult方法回调&#xff0c;在该方法判断三种状态&#xff1a;允…

android filehelper,为AndroidStudio开发mvp插件(MvpHelper)

如果觉得写mvp有点枯燥无味&#xff0c;我们可以做点 cool 的事情&#xff1a;做个 as 插件help.pngtodo-mvp: 基础的MVP架构。todo-mvp-loaders:基于MVP架构的实现&#xff0c;在获取数据的部分采用了loaders架构。todo-mvp-databinding: 基于MVP架构的实现&#xff0c;采用了…

android+5.q,MSM8909+Android5.1.1电池管理(2)--qpnp-linear-charger.txt驱动学习概要

MSM8909Android5.1.1电池管理(2)--qpnp-linear-charger.txt驱动学习概要参考文件\kernel\Documentation\power\qpnp-linear-charger.txt---下面是学习此文件\kernel\Documentation\devicetree\bindings\power\qpnp-linear-charger.txt1. 简介The QPNP linear charger drive…