Linux内核中增加系统调用获取指定进程相关数据

Posted by W-M on February 6, 2018

Linux下对线程的实现实际上是通过轻量级进程(LWP)来实现的,同一个进程下的多个LWP共享同一内存空间,每个LWP对应一个内核线程,内核线程是操作系统线程调度的基本单位。

如果用户线程与LWP之间的映射关系是1:1的话,每次线程调度都需要进入内核态。

为什么要为每个LWP映射一个内核线程呢?当需要访问内核空间的代码和数据执行一些内核态下才能进行的操作时(比如系统调用),LWP本身没有权限来完成这个工作,就需要内核线程来帮助我们完成。

问题:通过系统调用由用户态陷入内核态的时候,需要调用内核中的系统调用服务程序,这个执行内核中服务程序并返回数据给调用者的过程是不是由每个LWP映射的内核线程来完成的呢???

只有需要访问内核空间的时候才需要使用内核线程吗?在访问内核空间的时候需要进行用户态与内核态之间的转换,比较耗时。

需要进行用户态与内核态转换的操作示例:中断、系统调用、线程切换。

linux中每个进程都有一个属于自己的数据结构task_struct,其中含有pid和tgid两个字段,每个进程(包括同一个进程下的所有轻量级进程)都有自己唯一的pid,对于tgid,同一个进程下的所有轻量级进程的tgid肯定相同,不同进程下的轻量级进程的tgid不同。getPID()方法返回的是tgid,所以一个进程下的所有线程调用getpid()方法返回结果都是相同的。

每个task_struct中有一个指向mm_struct的指针,(同一个进程下的多个LWP的task_struct指向同一个mm_struct吗?)mm_struct用于描述一个进程虚拟地址空间的布局。

mm_struct其中含有两个字段为mm_users与mm_count。mm_users代表当前进程下的LWP的数量,mm_count代表引用当前mm_struct的内核线程的数量。尽管每个LWP对应一个内核线程,但是同一个进程下的所有内核线程在mm_count中视为1。mm_count有什么作用呢?

对Linux来说,用户进程和内核线程(kernel thread)都是task_struct的实例,唯一的区别是kernel thread是没有进程地址空间的,内核线程也没有mm描述符的,所以内核线程的tsk->mm域是空(NULL)。内核scheduler在进程context switching的时候,会根据tsk->mm判断即将调度的进程是用户进程还是内核线程。但是虽然kernel thread不用访问用户进程地址空间,但是仍然需要page table来访问kernel自己的空间。但是幸运的是,对于任何用户进程来说,他们的内核空间都是100%相同的,所以内核可以’borrow’上一个被调用的用户进程的mm中的页表来访问内核地址,这个mm就记录在active_mm。

简而言之就是,对于kernel thread,tsk->mm == NULL表示自己内核线程的身份,而tsk->active_mm是借用上一个用户进程的mm,用mm的page table来访问内核空间。对于用户进程,tsk->mm == tsk->active_mm。

为了支持这个特别,mm_struct里面引入了另外一个counter,mm_count。刚才说过mm_users表示这个进程地址空间被多少线程共享或者引用,而mm_count则表示这个地址空间被内核线程引用的次数+1。

比如一个进程A有3个线程,那么这个A的mm_struct的mm_users值为3,但是mm_count为1,所以mm_count是process level的counter。维护2个counter有何用处呢?考虑这样的scenario,内核调度完A以后,切换到内核内核线程B,B ’borrow’ A的mm描述符以访问内核空间,这时mm_count变成了2,同时另外一个cpu core调度了A并且进程A exit,这个时候mm_users变为了0,mm_count变为了1,但是内核不会因为mm_users==0而销毁这个mm_struct,内核只会当mm_count==0的时候才会释放mm_struct,因为这个时候既没有用户进程使用这个地址空间,也没有内核线程引用这个地址空间。

关于内核线程: (每个LWP对应的内核线程也是执行一个单独指定的内核函数,功能比较单一吗???) 1、系统把一些重要的任务委托给周期性执行的进程 刷新磁盘高速缓存 交换出不用的页框 维护网络链接等待 2、内核线程与普通进程的差别 每个内核线程执行一个单独指定的内核函数 只运行在内核态 只使用大于PAGE_OFFSET的线性地址空间

内核线程对于异步IO的操作十分重要???

mm_struct用于描述一个进程虚拟地址空间的布局。 mm_struct中有一个字段mmap,指向了一个链表:即struct vm_area_struct *mmap。

mmap链表中的一个节点vm_area_struct记录了实际分配的一个内存区域。

vm_area_struct有以下几点作用:

  • 记录进程地址空间中哪些区域被使用了,而哪些区域空闲。
  • 如果进程地址空间中的一个区域被分配给了进程,则内核会创建一个对应的线性区,也叫虚拟内存区(VMA)。
  • 属于同一个进程地址空间的线性区形成一个链表。
  • 不同的VMA绑定不同的访问权限和属性:可读、可写、可执行、私有、共享、锁定等。
  • 不同的VMA中存放了不同类型的数据:代码、全局变量、只读数据、动态库…
  • 为了加速查找空闲虚拟内存区,Linux同时将同一进程的所有线性区组织成一棵红黑树。
  • 线性区的开始和结束都必须4KB对齐

注意:VMA只是对于进程虚拟内存区域的分配做管理(比如哪块虚拟内存只能存放哪些数据)和记录(记录哪些内存已经分配了出去,哪些内存未分配),并不真正的存储数据。

当应用程序向os中申请一块内存时,os先在VMA中查找哪些地址空间可用并分配给进程,只是先分配逻辑地址空间,并不分配实际的物理空间,等到真正向这块逻辑空间中有写入数据操作时,才为其真正的分配一块物理地址空间,这是操作系统拖延战术的体现。将VMA组织成红黑树的原因可能也是为了更快的查询哪些逻辑空间可用。

操作系统拖延战术的体现还有fork时的copy on write技术,先令fork后的子进程与父进程共享同一内存地址空间,等到子进程中真的有数据写入操作时才为其分配单独的物理空间。

当查找某块逻辑地址对应的真正数据时,需要通过页表来完成。linux中mm_struct中含有一项pgd_t * pgd指向页表的目录。

问题:每个变量的逻辑地址是不是存储在各个LWP私有空间中,而不是存在VMA中???

问题:Linux中每个LWP对应一个内核线程,为什么要为每个LWP对应一个内核线程呢?linux进程调度是基于内核线程实现的,那么是不是每个LWP切换的时候os调度的都是其对应的内核线程呢?内核线程与LWP显然具有不同的task_struct(内核线程task_struct中mm为null),那么内核线程与其对应的LWP之间是如何切换的呢?是不是LWP在工作在用户空间时不需要使用其对应的内核线程,在需要执行内核空间代码时才使用其对应的内核线程执行呢?

问题:linux最多支持多少进程,多少内核线程?

问题:用户进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度;这句话的意思是不是说linux中进程被os调度时,必须处于内核态???如果是的话,进程调度时是如何进入内核态的呢?通过相应时钟中断吗?

时钟中断由硬件发出,操作系统提供中断处理程序,这个中断处理程序具体由哪个进程或者内核线程来执行呢?