本文仅作为学习记录,非商业用途,侵删,如需转载需作者同意。
继续说task_struct 结构…
1.1、运行统计信息
参照员工工作情况,也需要了解进程的工作情况,例如进程在用户态、内核态消耗的时间,上下文切换次数等。
u64 utime;//用户态消耗的CPU时间
u64 stime;//内核态消耗的CPU时间
unsigned long nvcsw;//自愿(voluntary)上下文切换计数
unsigned long nivcsw;//非自愿(involuntary)上下文切换计数
u64 start_time;//进程启动时间,不包含睡眠时间
u64 real_start_time;//进程启动时间,包含睡眠时间
1.2、进程亲缘关系
任何一个进程都有父进程。
struct task_struct __rcu *real_parent; /* real parent process */
struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
- parent 指向其父进程。当它终止时,必须向它的父进程发送信号
- children 表示链表的头部,链表中的所有元素都是它的子进程
- sibling 用于把当前进程插入到兄弟链表中
通常情况下,real_parent 和 parent 是一样的,例如:
1、bash创建了一个进程,那进程的parent和real_parent就都是bash
2、bash上使用GDB来debug一个进程,这个时候GDB是parent,bash是这个进程的real_parent
1.3、进程权限
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
权限:我能操纵谁,谁能操纵我
操作者是:Subjective
被操作对象:Objective
“操作” 需要操作者和被操作者双方权限认证。
real_cred 说明谁能操作我这个进程。
cred 说明我这个进程能操作谁
cred 的定义如下:
struct cred {
......
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
......
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
......
} __randomize_layout;
上面大部分是:关于用户和用户组的信息
uid和gid:注释是real user/group id 。一般情况下,谁启动的进程,就是谁的id。 在权限审核的时候,这2个基本不起作用。
euid和egid:注释是effective user/group id。 当这个进程要操作消息队列、共享内存、信号量等对象的时候,就是在比较这个用户和组是否有权限。
fsuid和fsgid filesystem user/group id 。 这个是对文件操作会审核的权限。
一般来说,fsuid、euid和uid 是一样的
fsgid、egid和gid 也是一样的。
因为谁启动的进程,应该审核启动的用户到底有没有这个权限。
上图中的第一个和第二个流程比较容易理解。
第三个不能保存怎么解决呢?
通过chmod u+s program 命令,给这个游戏程序设置 set-user-id 的标识位,把游戏的权限变成 rwsr-xr-x 。
这个时候用户A 再启动这个游戏的时候,创建的进程 uid 当然还是用户A ,但是euid 和 fsuid 就不再是用户A了,因为看到了 set-user-id 标识,就改为文件所有者的id,也就是说 euid 和 fsuid 都改成用户B 了,这样游戏文件就能保存下来了。
capabilities 更细粒度的权限,用位图表示权限,在capability.h 可以找到定义的权限。
#define CAP_CHOWN 0
#define CAP_KILL 5
#define CAP_NET_BIND_SERVICE 10
#define CAP_NET_RAW 13
#define CAP_SYS_MODULE 16
#define CAP_SYS_RAWIO 17
#define CAP_SYS_BOOT 22
#define CAP_SYS_TIME 25
#define CAP_AUDIT_READ 37
#define CAP_LAST_CAP CAP_AUDIT_READ
cap_permitted 表示进程能够使用的权限,真正起作用的是 cap_effective。
cap_permitted 中可以包含 cap_effective 中没有的权限。
一个进程可以在必要的时候放弃自己的某些权限,这样更加安全,如果因为代码漏洞被攻破了,啥也干不了,就没法进一步突破。
cap_bset : capaility bounding set 。 是系统中所有进程允许保留的权限,如果这个集合中不存在某个权限,那么系统中的所有进程都没有这个权限。即使以超级用户权限执行的进程也是一样的。
其中的一个好处是:系统启动以后,将内核加载的权限去掉,那所有进程都不能加载内核模块,这样即使这台机器被攻破了,也做不了太多有害的事情。
cap_ambient 是比较新加入内核的,就是为了解决cap_inheritable 鸡肋的状况。
也就是非root用户进程使用 exec 执行一个程序的时候,如何保留权限的问题。
当执行 exec的时候,cap_ambient 会被添加到 cap_permitted 中,同时设置到cap_effective中。
1.4、内存管理
每个进程都有自己独立的虚拟内存空间,这里需要一个数据结构来表示,mm_struct
struct mm_struct *mm;
struct mm_struct *active_mm;
1.5、文件与文件系统
每个进程都有一个文件系统和打开文件的数据结构:
/* Filesystem information: */
struct fs_struct *fs;
/* Open file information: */
struct files_struct *files;
总结时刻
- 进程亲缘关系维护的数据结构,是一种很有参考价值的实现方式,内核中多个地方用到这个数据结构
- 进程权限中的setuid 的原理,比较重要,面试经常会考
评论
RCU(Read-Copy Update) :随意读,更新数据的时候,先复制一份副本,在副本上完成修改,再一次性的替换旧数据。Linux 内核实现的一种针对“读多写少 ”的共享数据的同步机制。