原文地址: http://blog.sina.com.cn/s/blog_6b94d5680101vkiv.html
引用这篇文章主要是因为,你经常会发现不root的情况下,完全无法ping通,而又不明原因。那么,就看看这个进程的控制,特别是其中的权限是如何被判定的吧
4.1 进程四要素
什么是进程?
1:有一段代码段供其执行,这代码段不一定是进程所专用,可以与其他进程公用。
2:
每个进程有其专用的系统空间的堆栈(栈)【这个栈是进程起码的“私有财产”】
3:在内核中,要有task_struct 进程控制块【
task_struct 进程控制块就是像是进程的财产登记卡,记录着进程所拥有的各项资源,只有有了task_struct,进程才能被内核所调度】
4:
拥有专有的用户空间【各进程的用户空间是相互独立的,但是各进程共享系统空间,且各进程不能直接(不通过系统调用)改变系统空间的内容】
如果有 1 2 3,完全没有用户空间 ==> 内核线程(kernel thread,比如kswapd)
如果有 1 2 3,没有独立的用户空间 ==>因为有用户空间但不是独立的,所以称为用户线程
linux 系统中,进程(process)和任务(task)是同一个意思;
Unix系统的进程在Intel 的技术资料中则称为“任务”;linux源自Unix和i386系统结构;
linux系统运行时的第一个进程是在系统初始化阶段“捏造”出来的,而此后的进程或线程则都是由一个已存在的进程像细胞分裂那样通过系统调用复制出来的,称为fork 或 clone
Intel的i386 通过任务门 和 进程的TSS段(任务状态段,TSS位于GDT中,包含了该进程的关键的状态信息【控制信息】,但linux却没有使用任务门)来在硬件上实现任务的切换;【但其实因为该处理器的CISC架构以及这种切换方式,不是特别的效率高的,其实任务切换可以做的更加的简单,i386的这个切换可以理解为一种“高级语言”,而我们在做操作系统时,往往使用效率更高的“低级语言 汇编等”】;
i386 CPU 要求软件去设置TR 与 TSS,TR指向CPU当前正在执行的任务进程的TSS,Intel的设计意图是 随着任务的切换而走马灯似的设置TR的内容;
CPU因中断或系统调用从用户空间进入系统空间时,会由于运行级别的变换而导致自动更换栈,不同的栈指针来自于当前任务的TSS中包含的栈指针(SS ESP);因为Linux系统中只用到了两个运行级别,即0级与3级,所以对于内核来说,TSS中只剩下0级的堆栈指针 即SS0 ESP0
Linux 系统在任务的切换过程中,因为效率的考虑,并不根据任务的切换去设置TR,而是直接修改TSS(Linux内核只使用这样一个TSS,用来保存当前任务的状态)中的SS0 ESP0==》铁打的营盘流水的兵,就一个TSS,就像一座营盘,建立后就不再动了,而里面的内容,也就是当前任务的系统堆栈指针,则随着进程的调度切换而流水似地变动。这是因为改变TSS中的SS0 ESP0所花的开销比通过装入TR以更换一个TSS要小得多。
Linux中TSS不是某个进程所独占的,他而是全局性的公共资源。内核中虽然有多个TSS,但是每个CPU就只有(使用)一个TSS,一经装入就不再变了。
Unix Linux系统中任务的切换只发生在系统空间中,这点很好理解,因为共享的系统空间中拥有各个进程的各种资源;
每个进程都有一个task_struct数据结构和一片用作系统空间栈的存储空间。内核在为每个进程分配一个task_struct结构时,实际上分配两个连续的物理页面(共8192个字节),这两个页面的底部用作进程的task_struct结构,而在结构的上面就用作进程的系统空间堆栈、
数据结构task_struct的大小约为1K字节,所以进程的系统空间的堆栈的大小约为7K字节,注意:系统空间堆栈的空间不像用户空间堆栈那样可以在运行时动态的扩展,如第2章所述,而是静态的确定了的,所以,在中断服务程序中、内核软中断服务程序以及其他设备驱动程序的设计中,应注意不能让这些函数嵌套太深【避免嵌套太深,导致栈的溢出】,同时在这些函数中也不适宜使用太多、太大的局部变量。
一个进程必定又是一个内核线程(内核线程的要求是 1.有代码段 2.有专用的系统栈 3.有task_struct数据接否);
内核中有一个宏操作current,它指向当前进程task_struct结构的指针。
接下来,可具体分析下task_struct结构,
struct task_struct {
volatile long state;
unsigned long flags;
int sigpending;
mm_segment_t addr_limit;
struct exec_domain *exec_domain;==>除
personality外,应用程序还有一些其他的版本间的差异,从而形成了不同的“执行域”,这个指针就是指向描述本进程所属的执行域的数据结构
volatile long need_resched;
unsigned long ptrace;
int lock_depth;
long counter;
long nice;
unsigned long policy;==>适用于本进程的调度政策,详见进程的调度与切换
struct mm_struct *mm;
int has_cpu, processor;
unsigned long cpus_allowed;
struct list_head run_list;
unsigned long sleep_time;
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm;
struct linux_binfmt *binfmt;
int exit_code, exit_signal;
int pdeath_signal;
==>这三个详见系统调用exit()与wait4()
unsigned long personality;
int dumpable:1;
int did_exec:1;
pid_t pid;
==>进程号
pid_t pgrp;
pid_t tty_old_pgrp;
pid_t
pgrp
;
pid_t tgid;
int leader;
//
pgrp pgrp leader 当一个用户登陆到系统时,就开始一个进程组(session),此后创建的进程都属于这同一个session。此外,若干进程可以通过“管道”组合在一起,如 ls | wc -l,从而形成进程组,详见“系统调用exec”一节
struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
struct list_head thread_group;
struct task_struct *pidhash_next;
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit;
struct semaphore *vfork_sem;
unsigned long rt_priority;==>优先级别以及“实时”优先级别,详见进程的调度与切换
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_incr;
struct timer_list real_timer;
struct tms times;
unsigned long start_time;
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS];
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1;
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;==>这8个主要与文件操作权限有关,见文件系统一章
int ngroups;
gid_t groups[NGROUPS];
kernel_cap_t cap_effective, cap_inheritable, cap_permitted; ==>
一般进程都不能"为所欲为",而是各自被赋予了
各种不同的权限。例如,一个进程是否可以通过系统调用ptrace()跟踪另一个进程,就是由该进程是否具有CAP_SYS_PTRACE授权决定的;一个进程是否有权重新引导操作系统,则取决于该进程是否具有CAP_SYS_BOOT授权。这样,就把进程的各种权限分细了,而不再是笼统地取决于一个进程是否是"特权用户"进程。每一种权限都由一个标志位代表,内核中提供了一个inline函数capable(),用来检验当前进程是否具有某种权限。如capable(CAP_SYS_BOOT)就是检查当前进程是否有权重引导操作系统〔返回非0表示有权)。值得注意的是,对操作权限的这种划分与文件访问权限结合在一起,形成了系统安全性的基础。在现今的网络时代,这种安全性正在变得愈来愈重要,而这方面的研究与发展也是一个重要的课题。
int keep_capabilities:1;
struct user_struct *user;
==>指向一个user_struct结构,该数据结构代表着进程所属的用户。注意这跟Unix内核中每个进程的user结构时两码事。Linux内核中user结构是非常简单的,详见“系统调用fork()”一节。
struct rlimit rlim[RLIM_NLIMITS];
==>这是一个结构数组,表明进程对各种资源的使用数量所受的限制,
struct rlimit {
unsigned long rlim_cur;
unsigned long rlim_max;
};
unsigned short used_math;
char comm[16];
int link_count;
struct tty_struct *tty;
unsigned int locks;
struct sem_undo *semundo;
struct sem_queue *semsleeping;
struct thread_struct thread;
struct fs_struct *fs;
struct files_struct *files;
spinlock_t sigmask_lock;
struct signal_struct *sig;
sigset_t blocked;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
//
parent_exec_id self_exec_id ==>与进程组session有关,详见系统调用exit()与wait4()
spinlock_t alloc_lock;
}
主要可分为状态、性质、资源、和组织等几大类;
volatile long state;表示进程当前运行的状态
unsigned long flags;;反映进程状态的信息,但不是运行状态,而是与管理有关的其他信息 ,见下面的注释
#define PF_ALIGNWARN 0x00000001
#define PF_STARTING 0x00000002
#define PF_EXITING 0x00000004
#define PF_FORKNOEXEC 0x00000040
#define PF_SUPERPRIV 0x00000100
#define PF_DUMPCORE 0x00000200
#define PF_SIGNALED 0x00000400
#define PF_MEMALLOC 0x00000800
#define PF_VFORK 0x00001000
#define PF_USEDFPU 0x00100000
再看下task_struct除上述外的其他的一下状态信息变量:
int sigpending==>表示进程收到了信号,但尚未处理,详见进程间通信中的信号一节
mm_segment_t addr_limit
==>虚拟地址空间的上限。对进程而言是其用户空间的上限,所以是0XBFFFFFFF,对内核线程而言则 是系统空间的上限,所以是0XFFFFFFFF。
volatile long need_resched
==>与调度有关,表示CPU从系统空间返回用户空间前夕要进行一次调度。
long counter
==>与调度有关,详见进程的调度与切换一节
unsigned long personality
==>由于Unix有许多不同的版本和变种,应用程序也就有了适用的范围,所以根据执行程序的不 同,每个进程都有其个性。
最后,每一个进程都不是孤立地存在于系统中,而总是根据不同的目的、关系和需要与其它的进程相联系。从内核的角度看,则是要按不同的目的和性质将每个进程纳入不同的组织中。第一个组织是由每个进程的"家庭与社会关系"形成的"宗族"或"家谱"。这是一种树型的组织,通过指针p_opptr、p_pptr、p_cptr、p_ysptr和p_osptr构成。其中p_opptr和p_pptr指向父进程的task_struct结构,p_cptr指向最"年轻"的子进程,而p_ysptr和p_osptr则分别指向其"弟弟"和"哥哥",从而形成一个子进程链。这些指针确定了一个进程在其"宗族"中的上、下、左、右关系,详见本章中对fork()和exit()的叙述。
到杂凑表中的某个队列中,同一队列中所有进程的pid都具有相同的杂凑值。由于杂凑表的使用,要找到pid为某个给定值的进程就很迅速了。
4.2 进程三部曲:创建、执行与消亡
4.3 系统调用fork() vfork() clone()
fork()与clone()的区别:
pid_t fork(void);
系统调用__clone()的主要用途是创建一个线程,这个线程可以是内核线程,也可以是用户线程。
__clone()也可以创建进程,有选择地复制父进程的资源。而fork()则是全面地复制。还有一个系统调用v
fork()其作用也是创建一个线程,但主要只是作为创建进程的中间步骤,目的在于提高创建时的效率,减少系统开销,其程序设计接口则与fork相同。
asmlinkage int sys_fork(unsigned long r4, unsigned long r5,unsigned long r6, unsigned long r7,
{
return do_fork(SIGCHLD, regs.regs[15], ®s, 0);
}
asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp,
{
if (!newsp)
newsp = regs.regs[15];
return do_fork(clone_flags, newsp, ®s, 0);
}
asmlinkage int sys_vfork(unsigned long r4, unsigned long r5, unsigned long r6, unsigned long r7,
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.regs[15], ®s, 0);
}
这三个系统调用 都是 通过调用
do_fork()来完成的,do_fork()通过不同的参数 在函数体内实现相关资源的拷贝;
下面来解读下这个函数
int do_fork(unsigned long clone_flags, unsigned long stack_start,
struct pt_regs *regs, unsigned long stack_size)
{
======================函数内解释部分========
===========================
#define CSIGNAL 0x000000ff
#define CLONE_VM 0x00000100
#define CLONE_FS 0x00000200
#define CLONE_FILES 0x00000400
#define CLONE_SIGHAND 0x00000800
#define CLONE_PID 0x00001000
#define CLONE_PTRACE 0x00002000
#define CLONE_VFORK 0x00004000
#define CLONE_PARENT 0x00008000
#define CLONE_THREAD 0x00010000
#define CLONE_SIGNAL (CLONE_SIGHAND | CLONE_THREAD)
==============================
==============================
int retval = -ENOMEM;
struct task_struct *p;
DECLARE_MUTEX_LOCKED(sem);
if (clone_flags & CLONE_PID) {
if (current->pid)
return -EPERM;
}
current->vfork_sem = &sem;
p = alloc_task_struct();
if (!p)
goto fork_out;
*p = *current;
retval = -EAGAIN;
if (atomic_read(&p->user->processes) >= p->rlim[RLIMIT_NPROC].rlim_cur)
goto bad_fork_free;
atomic_inc(&p->user->__count);
atomic_inc(&p->user->processes);
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
get_exec_domain(p->exec_domain);
if (p->binfmt && p->binfmt->module)
__MOD_INC_USE_COUNT(p->binfmt->module);
p->did_exec = 0;
p->swappable = 0;
p->state = TASK_UNINTERRUPTIBLE;
copy_flags(clone_flags, p);
p->pid = get_pid(clone_flags);
======================函数内解释部分===================================
task_struct结构中有个指针user,用来指向一个user_struct结构。
一个用户常常有许多个进程,所以有关用户的一些信息并不专属于某一个进程。这样,属于同一用户的进程就可以通过指针user共享这些信息。
显然,每个用户有且只有一个user_struct结构。结构中有个计数器__count对属于该用户的进程数量计数。
可想而知,内核线程并不属于某个用户,所以其task_struct中的user指引为0。
#define UIDHASH_BITS 8
#define UIDHASH_SZ (1 << UIDHASH_BITS)
static struct user_struct *uidhash_table[UIDHASH_SZ];
这是一个杂凑(hash)表。对用户名施以杂凑运算,就可以计算出一个下标而找到该用户的user_struct结构。
各进程的task_struct结构中还有个数组rlim,对该进程占用各种资源的数量作出限制,而
rlim[RLIMIT_NPROC]就规定了该进程所属的用户可以拥有的进程数量。所以,如果当前进程是一个用
户进程,并且该用户拥有的进程数量已经达到了规定的限制值,就再不允许它fork()了. 那么,对于不
属于任何用户的内核线程怎么办呢? 587行中的两个计数器就是为进程的总量而设的。
常数PID_MAX定义为0X8000。可见,进程号的最大值是0X7FFF 即32767。进程号0?299是为系统进程(包括内核线程)保留的,主要用于各种"保护神"进程。以上这段代码的逻辑并不复杂,我们就不多加解释了。
==================================
==================================
p->run_list.next = NULL;
p->run_list.prev = NULL;
if ((clone_flags & CLONE_VFORK) || !(clone_flags & CLONE_PARENT)) {
p->p_opptr = current;
if (!(p->ptrace & PT_PTRACED))
p->p_pptr = current;
}
p->p_cptr = NULL;
init_waitqueue_head(&p->wait_chldexit);
p->vfork_sem = NULL;
spin_lock_init(&p->alloc_lock);
p->sigpending = 0;
init_sigpending(&p->pending);
p->it_real_value = p->it_virt_value = p->it_prof_value = 0;
p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0;
init_timer(&p->real_timer);
p->real_timer.data = (unsigned long) p;
p->leader = 0;
p->tty_old_pgrp = 0;
p->times.tms_utime = p->times.tms_stime = 0;
p->times.tms_cutime = p->times.tms_cstime = 0;
#ifdef CONFIG_SMP
{
int i;
p->has_cpu = 0;
p->processor = current->processor;
for(i = 0; i < smp_num_cpus; i++)
p->per_cpu_utime[i] = p->per_cpu_stime[i] = 0;
spin_lock_init(&p->sigmask_lock);
}
#endif
p->lock_depth = -1;
p->start_time = jiffies;
======================函数内解释部分===================================
==================================
=======
=======
====================
retval = -ENOMEM;
if (copy_files(clone_flags, p))
goto bad_fork_cleanup;
if (copy_fs(clone_flags, p))
goto bad_fork_cleanup_files;
if (copy_sighand(clone_flags, p))
goto bad_fork_cleanup_fs;
if (copy_mm(clone_flags, p))
goto bad_fork_cleanup_sighand;
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
if (retval)
goto bad_fork_cleanup_sighand;
p->semundo = NULL;
p->parent_exec_id = p->self_exec_id;
p->swappable = 1;
p->exit_signal = clone_flags & CSIGNAL;
p->pdeath_signal = 0;
p->counter = (current->counter + 1) >> 1;
current->counter >>= 1;
if (!current->counter)
current->need_resched = 1;
retval = p->pid;
p->tgid = retval;
INIT_LIST_HEAD(&p->thread_group);
write_lock_irq(&tasklist_lock);
if (clone_flags & CLONE_THREAD) {
p->tgid = current->tgid;
list_add(&p->thread_group, ¤t->thread_group);
}
SET_LINKS(p);
hash_pid(p);
nr_threads++;
write_unlock_irq(&tasklist_lock);
if (p->ptrace & PT_PTRACED)
send_sig(SIGSTOP, p, 1);
wake_up_process(p);
++total_forks;
fork_out:
if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;
bad_fork_cleanup_sighand:
exit_sighand(p);
bad_fork_cleanup_fs:
exit_fs(p);
bad_fork_cleanup_files:
exit_files(p);
bad_fork_cleanup:
put_exec_domain(p->exec_domain);
if (p->binfmt && p->binfmt->module)
__MOD_DEC_USE_COUNT(p->binfmt->module);
bad_fork_cleanup_count:
atomic_dec(&p->user->processes);
free_uid(p->user);
bad_fork_free:
free_task_struct(p);
goto fork_out;
}
4.4 系统调用execve()
char buf[BINPRM_BUF_SIZE];
struct page *page[MAX_ARG_PAGES];
unsigned long p;
int sh_bang;
struct file * file;
int e_uid, e_gid;
kernel_cap_t cap_inheritable, cap_permitted, cap_effective;
int argc, envc;
char * filename;
unsigned long loader, exec;
}
int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs)
{
struct linux_binprm bprm;
struct file *file;
int retval;
int i;
file = open_exec(filename);
retval = PTR_ERR(file);
if (IS_ERR(file))
return retval;
bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);
memset(bprm.page, 0, MAX_ARG_PAGES*sizeof(bprm.page[0]));
bprm.file = file; -->保存打开文件的file结构指针
bprm.filename = filename;
bprm.sh_bang = 0;-->
bprm.loader = 0;
bprm.exec = 0;
if ((bprm.argc = count(argv, bprm.p / sizeof(void *))) < 0) {
allow_write_access(file);
fput(file);
return bprm.argc;
}
if ((bprm.envc = count(envp, bprm.p / sizeof(void *))) < 0) {
allow_write_access(file);
fput(file);
return bprm.envc;
}
retval = prepare_binprm(&bprm);
if (retval < 0)
goto out;
retval = copy_strings_kernel(1, &bprm.filename, &bprm);
if (retval < 0)
goto out;
bprm.exec = bprm.p;
retval = copy_strings(bprm.envc, envp, &bprm);
if (retval < 0)
goto out;
retval = copy_strings(bprm.argc, argv, &bprm);
if (retval < 0)
goto out;
retval = search_binary_handler(&bprm,regs);
if (retval >= 0)
return retval;
out:
allow_write_access(bprm.file);
if (bprm.file)
fput(bprm.file);
for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
struct page * page = bprm.page[i];
if (page)
__free_page(page);
}
return retval;
}