linux线程

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 linux线程,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

 线程是什么?

线程机制是现代编程技术中常用的一种抽象概念。该机制提供了在同一程序内共享内存地址空间运行的一组线程。这些线程还可以共享打开的文件和其他资源。线程机制支持并发程序设计技术( concurrent programming),在多处理器系统上,它也能保证真正的并行处理(parallelism )。

Linux实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux 把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属于自己的task_struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和其他一些进程共享某些资源,如地址空间)。

上述线程机制的实现与Microsoft Windows或是Sun Solaris等操作系统的实现差异非常大。这些系统都在内核中提供了专门支持线程的机制(这些系统常常把线程称作轻量级进程( lightweight processes))。“轻量级进程”这种叫法本身就概括了Linux 在此处与其他系统的差异。在其他的系统中,相较于重量级的进程,线程被抽象成一种耗费较少资源,运行迅速的执行单元。而对于Linux来说,它只是一种进程间共享资源的手段(Linux的进程本身就够轻量级了)。举个例子来说,假如我们有一个包含四个线程的进程,在提供专门线程支持的系统中,通常会有一个包含指向四个不同线程的指针的进程描述符。该描述符负责描述像地址空间、打开的文件这样的共享资源。线程本身再去描述它独占的资源。相反,Linux仅仅创建四个进程并分配四个普通的task_sturct结构。建立这四个进程时指定他们共享某些资源,这是相当高雅的做法。

创造线程

明确一点,站在linux内核角度而言,线程是一种进程,所以内核应该会采用clone的系统调用(区别就是 传递参数指定共享的资源)。

clone(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND,0);

上面的代码产生的结果和调用fork()差不多,只是父子俩共享地址空间、文件系统资源、文件描述符和信号处理程序。换个说法就是,新建的进程和它的父进程就是流行的所谓线程。

对比一下,一个普通的fork()的实现是:

clone (SIGHAND,0);

clone()参数标志
参数标志(自动省去 CLONE) 含义
FILES 父子进程共享打开文件
FS 父子进程共享文件系统信息
IDLETASK 将PID设置为0(只供idle进程使用)
NEWNS 为子进程创建新的命名空间
PARENT 指定子进程与父进程拥有一个父进程
PTRACE 继续调试子进程
SETTID 将TID回写至用户空间
SETTLS 为子进程创建新的TLS
SIGHAND 父子进程共享信号处理函数以及被阻断的信号
SYSVSEM 共享System V SEM_UNDO语义
THREAD 放入相同的线程组
VFORK 调用vfork,父进程将睡眠等待的子进程将其唤醒
UNTRACED 防止跟踪进程在子进程上强制执行PTRACE
STOP 以stop进程状态开始
SETTLS 为子进程创制新的TLS
CHILD_CLEARTID 消除子进程的TID
CHILD_SETTID 设置子进程的TID
PARENT_SETTID 设置父进程的TID
VM 父子进程共享地址空间

内核线程(简单介绍)

内核经常需要在后台执行一些操作。这种任务可以通过内核线程(kernel thread)完成——独立运行在内核空间的标准进程。内核线程和普通的进程间的区别在于内核线程没有独立的地址空间(实际上指向地址空间的mm 指针被设置为NULL)。它们只在内核空间运行,从来不切换到用户空间去。内核进程和普通进程一样,可以被调度,也可以被抢占。

Linux确实会把一些任务交给内核线程去做,像flush和 ksofirqd这些任务就是明显的例子。在装有Linux系统的机子上运行ps -ef命令,你可以看到内核线程,有很多!这些线程在系统启动时由另外一些内核线程创建。实际上,内核线程也只能由其他内核线程创建。内核是通过从kthreadd内核进程中衍生出所有新的内核线程来自动处理这一点的。在<linux/kthread.b>中申明有接口,于是,从现有内核线程中创建一个新的内核线程的方法如下:

struct task_struct* kthread_create(int (threadfn)(void*data),
void data*m
const char namefmt[]
...)

 新的任务是由kthread内核进程通过clone()系统调用而创建的。新的进程将运行threadfn函数,给其传递的参数为data。进程会被命名为namefmt,namefmt接受可变参数列表类似于printf()的格式化参数。新创建的进程处于不可运行状态,如果不通过调用wake_up_process()明确地唤醒它,它不会主动运行。创建一个进程并让它运行起来,可以通过调用kthread_run()来达到:

struct task_struct* kthread_run
int (threadfn)(void*data),
void* data,
const char namefmt[]
...)

 kthread_run应用

#define kthread_run(threadfn,data,namefmt,...)   \
({                                               \
struct task_struct* k;
                                                 \
k=kthread_create(threadfn,data,namefmt,##_VA_ARGS_)\
if(!IS_ERR(k))                                   \
wake_up_process(k);                              \
                                                 \
k;                                               \
}

进程终结

一般来说,进程的析构是自身引起的。它发生在进程调用exit()系统调用时,既可能显式地调用这个系统调用,也可能隐式地从某个程序的主函数返回(其实C语言编译器会在main(函数的返回点后面放置调用exit)的代码)。当进程接受到它既不能处理也不能忽略的信号或异常时,它还可能被动地终结。不管进程是怎么终结的,该任务大部分都要靠do_exit((定义于kernelexit.c)来完成,它要做下面这些烦琐的工作:

1)将tast_struct中的标志成员设置为PF_EXITING.

2)调用del_timer_sync(删除任一内核定时器。根据返回的结果,它确保没有定时器在排队,也没有定时器处理程序在运行。

3)如果 BSD的进程记账功能是开启的,do_exit()调用acct_update_integrals()来输出记账信息。

4)然后调用exit_mm()函数释放进程占用的mm_struct,如果没有别的进程使用它们(也就是说,这个地址空间没有被共享),就彻底释放它们。

5)接下来调用sem__exit()函数。如果进程排队等候IPC信号,它则离开队列。

6)调用exit_files()和exit_fs(),以分别递减文件描述符、文件系统数据的引用计数。如果其中某个引用计数的数值降为零,那么就代表没有进程在使用相应的资源,此时可以释放。

7)接着把存放在.task_struct的exit_code成员中的任务退出代码置为由exit()提供的退出代码,或者去完成任何其他由内核机制规定的退出动作。退出代码存放在这里供父进程随时检索。

8)调用exit_notifyO)向父进程发送信号,给子进程重新找养父,养父为线程组中的其他线程或者为init进程,并把进程状态(存放在task_struct结构的exit_state中)设成EXIT_ZOMBIE。

9) do_exitO调用schedule()切换到新的进程,因为处于EXIT_ZOMBIE 状态的进程不会再被调度,所以这是进程所执行的最后一段代码。do exit永不返回。

孤儿进程的进退

如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白地耗费内存。前面的部分已经有所暗示,对于这个问题,解决方法是给子进程在当前线程组内找一个线程作为父亲,如果不行,就init做它们的父进程。在do_exit()中会调用exit_notify(),该函数会调用forget_original_parent(),而后者会调用find_new_reaper()来执行寻父过程:

static struct task_struct*find_new_reaper(struct task_struct* father)
{

struct pid_namespace*pid_ns=task_active_pid_ns(father);
struct task_struct *thread;

thread=father;
while_each_thread(father,thread)
{
if(thread->flags&PF_EXITING)
continue;
if(unlikely(pid_ns->child_reaper=-father))
pid_ns->child_reaper=thread;
return thread;
}


if(unlikely(pid_ns-<child_reaper==father))
{
write_unlike_irq(&tasklist_lock)'
if(unlikely(pid_ns==&init_pid_ns))
panic("Attempted to kill init");

pid_ns->child_reaper=init_pid_ns.child_reaper;
}

return pid_ns->child_reaper;
}

这段代码试图找到进程所在的线程组内的其他进程。如果线程组内没有其他的进程,它就找到并返回的是init进程。现在,给子进程找到合适的养父进程了,只需要遍历所有子进程并为它们设置新的父进程: 

专业名词解释

idle进程:

简单的说idle是一个进程,其pid号为 0。其前身是系统创建的第一个进程,也是唯一一个没有通过fork()产生的进程。在smp系统中,每个处理器单元有独立的一个运行队列,而每个运行队列上又有一个idle进程,即有多少处理器单元,就有多少idle进程。系统的空闲时间,其实就是指idle进程的”运行时间”。idle进程pid==o,也就是init_task.

kernel的命名空间

命名空间(Namespace)是 Linux 内核的一个特性,它对内核资源进行分区,使得一组进程看到一组资源,而另一组进程看到一组不同的资源。该功能的工作原理是为一组资源和进程使用相同的命名空间,但这些命名空间引用不同的资源。资源可能存在于多个空间中。此类资源的示例包括进程 ID、主机名、用户 ID、文件名以及一些与网络访问和进程间通信相关的名称。

TLS

TLS的定义就不言而喻。TLS全称为Thread Local Storage,即线程本地存储。在单线程模式下,所有整个程序生命周期的变量都是只有一份,那是因为只是一个执行单元;而在多线程模式下,有些变量需要支持每个线程独享一份的功能。这种每线程独享的变量放到每个线程专有的存储区域,所以称为线程本地存储(Thread Local Storage)或者线程私有数据(Thread Specific Data)

System V

SysV 信号量集  : 一个信号量数组;  命令 ipcs – s 查看

emget : 创建或获取一个信号量集 . 第2个参数, 指定N个信号量;

semop:  修改一个或一批信号量. 相当于 posix 中的 sem_wait  和 sem_post 综合体

              每个信号量由struct sembuf 控制. 

              sem_num 表示第N个信号量. 

              sem_op 可以是 > 0 , 0 , <0 , 相当于  sem_post == 1, sem_wait ==  -1;

              sem_flg : IPC_NOWAIT  不阻塞, SEM_UNDO 还原;

sem_ctl : 给信号量赋值, 删除, 状态等

SEM_UNDO: 如果指定了. 不论当前进程是否正常退出 都将还原此操作的 sem_op 值. 用于以防万一异常结束的进程

僵死进程

僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源

BSD记账

 多数UNIX系统提供了一个执行进程记帐的选项。当被启动时,内核每次在一个进程终止时写一个记帐记录。这些记帐记录一般是命令名的少量二进制数据、使用 的CPU时间量、用户ID和组ID、开始时间

ipc信号

进程间通信是指在不同进程之间传播或交换信息,在Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间,进程之间不能相互访问。必须通过内核才能进行数据交换。如图
 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/129660.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!