主要内容包括:

1、进程描述符中Realtime Mutex相关数据结构的初始化

2、子进程如何复制父进程的credentials

3、per-task delay accounting的处理

4、子进程如何复制父进程的flag

七、初始化Realtime Mutex相关的成员

static void rt_mutex_init_task(struct task_struct *p)
{
    raw_spin_lock_init(&p->pi_lock);
#ifdef CONFIG_RT_MUTEXES
    p->pi_waiters = RB_ROOT;
    p->pi_waiters_leftmost = NULL;
    p->pi_blocked_on = NULL;
    p->pi_top_task = NULL;
#endif
}

Mutex是一种人民群众喜闻乐见的内核同步方式,不过real time mutex是什么呢?real time mutex是被设计用来支PI-futexes的。什么是PI?什么又是futex?PI是优先级继承(Priority Inheritance),该方法是用来解决优先级翻转问题的。什么是优先级翻转(Priority Inversion)?它是一种调度延迟现象。一般而言,调度器总是优先调度到优先级高的进程(线程),但是,当同步资源被较低优先级的进程所拥有(也就是说持有锁),高优先级的进程未能获取该同步资源,这时候,高优先级进程要等到持有锁的进程释放该资源后才能被调度到。下面的图片更加详细的描述了该问题:

低优先级进程和高优先级进程都需要访问一个公共资源,因此需要一个mutex来保护对该公共资源的访问。

T0时刻,只有低优先级进程处于可运行状态,运行过程中,在T1时刻,低优先级进程访问公共资源,并且持有了mutex lock,T2时刻,由于外部事件,导致中优先级进程进入可运行状态,中优先级进程就绪进入可运行状态,由于优先级高于正在运行的低优先级进程,低优先级进程被抢占(没有unlock mutex),中优先级进程被调度执行。同样地T3时刻,由于外部事件,高优先级进程抢占中优先级进程。高优先级进程运行到T4时刻,需要访问公共资源,但该资源被更低优先级的进程所拥有,高优先级进程只能被挂起等待该资源,而此时处于可运行状态的线程中,中优先级进程由于优先级高而被调度执行。

上述现象中,优先级高的进程要等待优先级低的进程完之后才能被调度,更加严重的是如果中优先级进程执行很费时的操作,显然高优先级进程的被调度时机就不能保证,整个实时调度的性能就很差了。

为了解决上述由于优先级翻转引起的问题,很多操作系统引入了优先级继承(Priority Inheritance)的解决方法。优先级继承的方法是这样的,当高优先级进程在等待低优先级的进程程占用的竞争资源时,为了使低优先级的进程能够尽快获得调度运行(以便释放高优先级进程需要的竞争资源),由操作系统kernel把低优先级进程的优先级提高到等待竞争资源高优先级进程的优先级。

OK,了解完这些内容之后,我们回到了futex。linux内核提供了一个叫做快速用户空间互斥(Fast User-Space Mutexes)的锁的机制,简称futex,通过这样的机制用户空间程序可以实现对互斥资源的快速访问。为什么提供futex这样的机制?如何使用?在用户空间如何互斥?为何能够快速?问题太多了,下次我会启动一个专题来描述futex。

八、process credentials

retval = -EAGAIN;
    if (atomic_read(&p->real_cred->user->processes) >=
            task_rlimit(p, RLIMIT_NPROC)) {
        if (p->real_cred->user != INIT_USER &&
            !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
            goto bad_fork_free;
    }
    current->flags &= ~PF_NPROC_EXCEEDED;

retval = copy_creds(p, clone_flags);
    if (retval < 0)
        goto bad_fork_free;

上面的这段代码主要是处理如何复制新创建进程的credentials。在task struct数据结构中,下面的数据结构描述了进程这个对象的credentials:

const struct cred __rcu *real_cred; /* objective and real subjective task
                     * credentials (COW) */
    const struct cred __rcu *cred;    /* effective (overridable) subjective task
                     * credentials (COW) */

虽然本网站的process credentials描述了一些进程credentials的相关知识,但那是基于2.6.29版本之前的描述。随着内核的演进,从2.6.29版本开始,内核对于credential有了进一步的抽象。首先,credential不再是进程特有的,而是内核对象(进程、file、socket等)的一个属性集合。而这些内核对象的属性被分成两类:一类是该对象作为动作的发起者,操作其他内核对象时候需要用的credential属性,被称为subjective context。另外一类是本内核对象被其他内核对象操作时候需要用到的credential属性,被称为objective context。举一个简单的例子:对于内核中的进程对象,可能存在下面的动作:

1、该进程访问文件系统中的文件

2、在进行文件的asynchronous I/O操作的时候,文件会向进程发送信号。

对于进程这个内核对象而言,在上面的场景1中,要使用进程的subjective context,而在场景2中,使用进程的objective context。内核中,struct cred就是对credential的抽象,进程描述符(struct task_struct)、文件描述符(struct file )这些内核对象都需要定义一个struct cred的成员来代表该对象的credential信息。OK,了解了上述信息之后,我们可以继续讲述task struct数据结构中的credentials成员。在进程对象在操作其他内核对象时候使用cred成员,而在其他对象操作该进程对象的时候,需要获取该进程的credential的时候,需要使用real_cred成员。

在copy process credential之前首先进行资源检查

1、该用户是否已经创建了太多的进程,如果没有超出了resource limit,那么继续fork

2、如果超出了resource limit,但user ID是root的话,没有问题,继续

3、如果不是root,但是有CAP_SYS_RESOURCE或者CAP_SYS_ADMIN的capbility,也OK,否则的话fork失败

检查完成之后就正式开始copy credential了(注:略去CONFIG_KEYS的代码,内核中支持密钥相关的操作值得用一篇单独的文档来描述,此外,为了降低文档长度,删除了debug相关的代码):

int copy_creds(struct task_struct *p, unsigned long clone_flags)
{
    struct cred *new;
    int ret;

if ( clone_flags & CLONE_THREAD  ) {  //创建线程相关的代码
        p->real_cred = get_cred(p->cred);
        get_cred(p->cred);      //对credential描述符增加两个计数,因为新创建线程的cred和real_cred都指向父进程的credential描述符
        atomic_inc(&p->cred->user->processes); //属于该user的进程/线程数目增加1
        return 0;
    }

new = prepare_creds();       //后段的代码是和fork进程相关。prepare_creds是创建一个当前task的subjective context(task->cred)的副本。
    if (!new)
        return -ENOMEM;

if (clone_flags & CLONE_NEWUSER) {//如果父子进程不共享user namespace,那么还需要创建一个新的user namespace
        ret = create_user_ns(new);
        if (ret < 0)
            goto error_put;
    }

atomic_inc(&new->user->processes);
    p->cred = p->real_cred = get_cred(new); //和线程的处理类似,只不过进程需要创建一个credential的副本
    return 0;

error_put:
    put_cred(new);
    return ret;
}

对于创建线程,所谓copy,也就是共享,在之前的task struct进行copy的时候,credential相关的两个指针都已经是指向同样的struct cred数据结构,完成了共享的操作(被创建进程/线程的real_cred指向其父进程的real_cred,被创建进程/线程的cred指向其父进程的cred),这些需要做一些修正:

1、被创建进程/线程的real_cred指向其父进程的cred。具体原因TODO

2、修正credential描述符的reference count

对于创建进程,内核会分配一个新的cred描述符,copy 父进程的credentials,也就是说,不是共享cred描述符,而的确是copy的动作了。如果本次fork也携带了CLONE_NEWUSER参数,打算创建一个新的user namespace,那么父子进程的username space也需要独立开来,

九、进程创建总数限制

retval = -EAGAIN;
    if (nr_threads >= max_threads)
        goto bad_fork_cleanup_count;

nr_threads是系统当前的线程数目;max_threads是系统允许容纳的最大的线程数。由于资源(CPU、memory)受限,系统不可能无限制的创建线程,否则,系统的memory可能会被进程的内核栈消耗掉。

在系统初始化的时候(fork_init),会根据当前系统中的memory对该值进行设定。

max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE);

if (max_threads < 20)  // 最小也会被设定为20
        max_threads = 20;

max_threads可以由用户进行设定。在/proc/sys目录下保存着若干的内核参数,该目录下的kernel/threads-max文件就是对系统内的可以创建的最大线程数的限制。

十、模块处理

if (!try_module_get(task_thread_info(p)->exec_domain->module))
        goto bad_fork_cleanup_count;

struct thread_info数据结构中的exec_domain成员指向了当前进程/线程的execution domain descriptor。linux kernel有一个很好的设计就是允许在其他操作系统上编译的程序在GNU/linux上执行。对于DOS或者Windows这样的操作系统,GNU/linux支持起来有些困难,多半是通过用户空间的仿真来做。但是对于POSIX兼容的操作系统,由于接口API是相同的,GNU/linux应该不会花费太多的力气,只需要处理系统调用的具体细节问题或者各种信号的编码问题。这些信息在kernel中用struct exec_domain抽象。

既然是共享了父进程的exec_domain,那么需要通过try_module_get去增加reference count(具体的copy在copy thread info的时候已经完成了)。

十一、per-task delay accounting的处理

delayacct_tsk_init(p);

delayacct是一个缩写,是指per-task delay accounting。这个feature是统计每一个task的等待系统资源的时间(例如等待CPU、memeory或者IO)。这些统计信息有助于精准的确定task访问系统资源的优先级。

一个进程/线程可能会因为下面的原因而delay:

1、该进程/线程处于runnable,但是等待调度器调度执行

2、该进程/线程发起synchronous block I/O,进程/线程处于阻塞状态,直到I/O的完成

3、进程/线程在执行过程中等待page swapping in。由于内存有限,系统不可能把进程的各个段(正文段、数据段等)都保存在物理内存中,当访问那些没有在物理内存的段的地址的时候,就会有磁盘操作,导致进程delay,这里有个专业的术语叫做capacity misses

4、进程/线程申请内存,但是由于资源受限而导致page frame reclaim的动作。

系统为何对这些delay信息进行统计呢?主要让系统确定下列的优先级的时候更有针对性:

1、task priority。如果该进程/线程长时间的等待CPU,那么调度器可以调高该任务的优先级。

2、IO priority。如果该进程/线程长时间的等待I/O,那么I/O调度器可以调高该任务的I/O优先级。

3、rss limit value。引入虚拟内存后,每个进程都拥有4G的地址空间。系统中有那么多进程,而物理内存就那么多,不可能让每一个进程虚拟地址(page)都对应一个物理地址(page frame)。没有对应物理地址的那部分虚拟地址的实际内容应该保存在文件或者swap设备上,一旦要访问该地址,系统会产生异常,并处理好分配page frame,页表映射,copy磁盘内容到page frame等一系列动作。rss的全称是resident set size,表示有物理内存对应的虚拟地址空间。由于物理内存资源有限,各个进程要合理使用。rss limit value定义了各个进程rss的上限。

struct task_delay_info *delays;

进程描述符中的delays成员记录了该task的delay统计信息,delayacct_tsk_init就是对该数据结构进程初始化。本文先简单描述概念,后续会有专门的文件来描述进程的统计信息。

十二、复制进程描述符的flag

static void copy_flags(unsigned long clone_flags, struct task_struct *p)
{
    unsigned long new_flags = p->flags;

new_flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
    new_flags |= PF_FORKNOEXEC;
    p->flags = new_flags;
}

copy_flags函数用来copy进程的flag,大部分的flag都是直接copy,但是下面的几个是例外:

1、PF_SUPERPRIV,这个flag是标识进程曾经使用了super-user privileges(并不表示该进程有超级用户的权限)。对于新创建的进程,当然不会用到super-user privileges,因此要清掉。

2、清除PF_WQ_WORKER标识。PF_WQ_WORKER是用来标识该task是一个workqueue worker。如果新创建的内核线程的确是一个workqueue worker的话,那么在其worker thread function(worker_thread)中会进行设定的。具体worker、workqueue等概念请参考Concurrency-managed workqueues相关的描述。

3、设定PF_FORKNOEXEC标识,表明本进程/线程正在fork,还没有执行exec的动作。

process credentials(三)的更多相关文章

  1. process credentials(一)

    一.介绍 当linux系统中的一个进程运行起来的时候,总是要访问系统的资源,访问文件或者向其他的进程发送信号.系统是否允许其进行这些操作?系统是根据什么来判断该进程的权限?这些问题是和进程信任状(pr ...

  2. process credentials(二)

    一.前言 为什么要写一个关于进程如何创建的文档?其实用do_fork作为关键字进行索引,你会发现网上的相关文档数以万计.作为一个内核工程师,对进程以及进程相关的内容当然是非常感兴趣,但是网上的资料并不 ...

  3. Linux process authority、the security risks in running process with high authority

    catalog . Linux进程权限原理 . 最小权限原则 - 进程降权运行最佳实践 . 进程权限控制包含的攻防向量 . 进程高权限安全风险检查技术方案 1. Linux进程权限原理 我们知道,Li ...

  4. Linux内核设计与实现 总结笔记(第三章)进程

    进程管理 进程:处于执行期的程序. 线程:在进程中活动的对象 虚拟机制 虚拟处理器:多个进程分享一个处理器 虚拟内存:多个线程共享虚拟内存 一.进程描述符和任务结构 进程存放在双向循环链表中(队列), ...

  5. hadoop入门级总结三:hive

    认识hive  Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供完整的SQL查询功能,可以将SQL语句转换为MapReduce任务运行  Hive是建立在 ...

  6. Java_Runtime&Process&ProcessBuilder

    目录 一.Runtime类 二.Process类 三.ProcessBuilder类 在Java中想调用外部程序,或者执行命令和可运行文件时,网上的典型实例一般都是通过Runtime.getTime( ...

  7. nodejs(三) --- nodejs进程与子进程

    嗯,对于node的学习还远远不够,这里先做一个简单的api的记录,后续深入学习. 第一部分:nodejs中的全局对象之process进程对象 在node中的全局对象是global,相当于浏览器中的wi ...

  8. Python程序中的进程操作-开启多进程(multiprocess.process)

    目录 一.multiprocess模块 二.multiprocess.process模块 三.process模块介绍 3.1 方法介绍 3.2 属性介绍 3.3 在windows中使用process模 ...

  9. linux内核分析之进程地址空间管理

    1.struct task_struct 进程内核栈是操作系统为管理每一个进程而分配的一个4k或者8k内存大小的一片内存区域,里面存放了一个进程的所有信息,它能够完整的描述一个正在执行的程序:它打开的 ...

随机推荐

  1. Discuz常见小问题-如何关闭验证码

    进入后台,在防灌水,验证设置中可以切换哪些情况下是否使用验证码 如果启用验证码,也客户修改验证码的难度,样式.最后点击提交,完成之后可以退出到前台,测试是否能够不用验证码自动登录

  2. windows安装mycat(转)

    http://blog.csdn.net/sc9018181134/article/details/53063798 1.先到github上下载mycat 2.下载完成后,解压.应该是这样一个样子 3 ...

  3. springcloud报错Cannot execute request on any known server

    启动springcloud服务中心,提示这个错误:Cannot execute request on any known server 网上一般说是修改application.properties配置 ...

  4. 微软BI 之SSAS 系列 - 在SQL Server 2012 中开发 Analysis Services Multidimensional Project

    SQL Server 2012 中提供了开发 SSAS 项目的两种模型,一种是新增加的 Tabular Model 表格模型,另一种就是原始的 Multidimensional Model 多维模型. ...

  5. Windows中snmputil.exe工具的使用

    一.检查windows 系统是否安装了 snmp 组件.可以在 cmd 中输入"net start snmp" 就可以出现显示信息了 二. snmputil,就是程序名拉,呵呵. ...

  6. spring-boot 实现文件上传下载

    @Controller public class FileUploadCtrl { @Value("${file.upload.dir}") private String path ...

  7. sqlalchemy结果转json

    网上搜了下,http://blog.csdn.net/liu_xing_hui/article/details/8956107 介绍的很详细,自动一个Encoder给json的dump方法使用,能够实 ...

  8. mahout做推荐时uid,pid为string类型

    很幸运找到这篇文件,解了燃眉之急. http://blog.csdn.net/pan12jian/article/details/38703569 mahout做推荐的输入只能是long类型,但在某些 ...

  9. 连通性问题--Algorithms IN C读书笔记

    近期在看<Algorithms IN C>这本书.刚開始看,读的是英文版的.感觉作者的叙述有点不太easy理解.就找了一本中文版的来看,发现还是看英文版的比較好.先看了第一章的大部分,后面 ...

  10. Android sdk content loader 0%

    打开Eclipse以后,一直在Android sdk content loader 0%,等了很长时间都没有变,解决的方法是Project->Clean->Clean all projec ...