Pid_namespace分析

1      概述

Pid namespace是对进程pid的容器虚拟化,从pid的维度实现容器间的隔离。即在一个容器中只能看到属于该pidns的pid,从而在某种程度上实现了进程间的隔离。

在容器中只能看到容器内的pid,但在宿主机上可以看到所有进程的pid,所以从看到的pid号的角度,这是有层级关系的,对应的,pidns在实现上也是有层级关系的,在高层次pidns中可以看到低层次pidns的信息,反之则不行。一个简单的示意图如下所示:

通过箭头连接的,表示同一个进程在不同的pidns中的pid表示。

说明:文本的代码基于Linux 3.4。

2      Pidns的管理

2.1      关键数据结构

通过上面的描述,在引入pidns后,要获取一个指定的进程,除了通过pid号外,还必须指明pidns,这样才可以确定唯一的进程。Pidns是如何管理来实现这一目的的,这主要通过pid_namespace结构体实现。

structpid_namespace {

        struct kref kref;                                 // 引用计数

        struct pidmap pidmap[PIDMAP_ENTRIES];       // pid分配的bitmap,为1表示已分配

        int last_pid;                                      // 记录上次分配的pid,默认当前分配的pid=last_pid+1

        unsigned int nr_hashed;

        struct task_struct *child_reaper;     // 父进程结束后,需要该child_reaper进程对其托管

        struct kmem_cache *pid_cachep;   // 用于分配pid结构的slab缓存

        unsigned int level;                           // 记录该pidns的深度

        struct pid_namespace *parent;        // 父pidns

#ifdefCONFIG_PROC_FS

        struct vfsmount *proc_mnt;

#endif

#ifdefCONFIG_BSD_PROCESS_ACCT

        struct bsd_acct_struct *bacct;

#endif

        struct work_struct proc_work;

        gid_t pid_gid;

        int hide_pid;

        int reboot;       /* group exit code if this pidns was rebooted */

};

 

这里比较重要的成员变量就是pidmap,它表示在该pidns中pid的分配情况,每pidns中独立的pidmap就决定了在一个pidns中,进程的pid是可以重新从1开始计数的。这是一种典型的位图的应用,在32位linux中一页的大小是 4k=4*1028*8位=32768位,所以一个page可以存储32768个bit位,而linux中默认pid的最大值也是32768,所以正好可以用一个page来表示进程pid的分配情况。

struct pidmap {

       atomic_t nr_free;       // 该bitmap中还有多少位为0,也就是还能分配多少pid

       void *page;                 // 存储该bitmap的page

};

        但是如果系统中最大的pid超过了32768,那个一个page就无法表示所有的pid分配情况了,所以在pid_namespace中,pidmap是一个数组,就是为了出现这种情况时,可以用多个page来存储pid的位图。

        一个进程对应一个task struct,但是这个进程在多个pidns中可以看到不同的pid,对于这些pid的管理,主要通过两个结构体来实现。

struct pid

{

        atomic_t count;

        unsigned int level;                   // 这个pid的深度

        /* lists of tasks that use this pid */

        struct hlist_head tasks[PIDTYPE_MAX];     // 使用这个pid的进程链表

        struct rcu_head rcu;

        struct upid numbers[1];          // 这个pid在不同命名空间中的显示

};

        这里最重要的成员变量就是numbers数组,它表示一个进程在每个namespace里的id,这里的id就是getpid()所得到的值。Numbers[0]表示最顶层的namespace,level=0,numbers[1]表示level=1的namespace,依此类推。

        这里numbers数组的长度为1,但是因为这是结构体的最后一个成员,所以通过在扩展结构体的存储空间大小,就可以无限制的根据需要扩展数组的长度,这种根据需要动态设置数组长度,在linux内核中是一种比较常见的方式。因为这里有一个成员level表示pid的深度,所以这里numbers数组的实际长度就会是level。

        另外,tasks[PIDTYPE_MAX]链表头也是比较容易产生困惑的地方。根据上面的理解,我们不难得出这样的分析:内核中的进程通过唯一的task_struct标示,但这个进程可以在不同的pidns中体现为不同的pid,这些pid用pid结构体来管理,所以一个进程对应唯一的一个pid结构体。但是为什么一个pid结构体会被多个进程使用呢?这实际上是内核中一些机制带来的副作用:父进程fork出子线程,然后子线程去调用exec,在这调用exec函数的过程中,首先子线程发信号使得父进程停止,子线程去attach父进程pid结构,最后再release父进程,在段代码中,父进程和子线程会共用一个pid结构。因为有这种可能性的存在,所以pid结构体也不得不处理这样的情况。

        再来看下upid的结构,

struct upid {

        /* Try to keep pid_chain in the samecacheline as nr for find_vpid */

        int nr;                                        //pid的数值

        struct pid_namespace *ns;      // 所在命名空间

        struct hlist_node pid_chain;   // 链表节点

};

        Upid的结构很简单,这里n

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以上C语言代码定义了一个PID控制器并进行了初始化。其中,PID_init函数用于初始化PID控制器,参数包括PID控制器结构体指针、控制模式、PID参数、最大输出和最大积分输出。PID控制器结构体包含了当前误差、累积误差、上次误差和输出等信息。 为了改进该代码,我们可以考虑以下几点: 1. 添加错误处理机制:在参数输入错误或者内部出现错误的情况下,应该给出相应的错误提示,避免程序崩溃。 2. 添加限制条件:在计算PID输出时应该考虑到输出范围和积分项的限制,避免输出超过设定范围或者积分项过大。 3. 修改参数输入方式:可以通过文件读取或者命令行输入等方式,将PID参数从代码中分离出来,方便参数的修改和调试。 4. 添加多种控制模式:根据不同的应用场景,可以添加多种控制模式,如位置控制、速度控制等,提高控制器的适用性。 改进后的代码如下所示: ``` #define PID_POSITION 0 #define PID_VELOCITY 1 typedef struct { uint8_t mode; // 控制模式,位置控制或速度控制 fp32 error; // 当前误差 fp32 last_error; // 上次误差 fp32 sum_error; // 累积误差 fp32 max_output; // 最大输出 fp32 max_ioutput; // 最大积分输出 fp32 kp; // 比例系数 fp32 ki; // 积分系数 fp32 kd; // 微分系数 } pid_type_def; void PID_init(pid_type_def *pid, uint8_t mode, fp32 kp, fp32 ki, fp32 kd, fp32 max_output, fp32 max_ioutput); void PID_reset(pid_type_def *pid); fp32 PID_calc(pid_type_def *pid, fp32 setpoint, fp32 feedback, fp32 dt); void PID_init(pid_type_def *pid, uint8_t mode, fp32 kp, fp32 ki, fp32 kd, fp32 max_output, fp32 max_ioutput) { pid->mode = mode; pid->kp = kp; pid->ki = ki; pid->kd = kd; pid->max_output = max_output; pid->max_ioutput = max_ioutput; pid->error = 0.0f; pid->last_error = 0.0f; pid->sum_error = 0.0f; } void PID_reset(pid_type_def *pid) { pid->error = 0.0f; pid->last_error = 0.0f; pid->sum_error = 0.0f; } fp32 PID_calc(pid_type_def *pid, fp32 setpoint, fp32 feedback, fp32 dt) { fp32 output = 0.0f; pid->error = setpoint - feedback; fp32 delta_error = pid->error - pid->last_error; pid->sum_error += pid->error * dt; if (pid->mode == PID_POSITION) { output = pid->kp * pid->error + pid->ki * pid->sum_error + pid->kd * delta_error / dt; } else if (pid->mode == PID_VELOCITY) { output = pid->kp * pid->error - pid->ki * feedback + pid->kd * delta_error / dt; } // 限制输出范围 if (output > pid->max_output) { output = pid->max_output; } else if (output < -pid->max_output) { output = -pid->max_output; } // 限制积分项 if (pid->sum_error > pid->max_ioutput) { pid->sum_error = pid->max_ioutput; } else if (pid->sum_error < -pid->max_ioutput) { pid->sum_error = -pid->max_ioutput; } pid->last_error = pid->error; return output; } ``` 改进后的代码添加了错误处理机制,限制条件和多种控制模式,使得控制器更加健壮和灵活。同时,参数输入方式也可以根据实际情况进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值