关于linux系统如何实现fork的研究(二)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/
引言
前一篇关于linux系统如何实现fork的研究(一)通过代码已经说明了从用户态怎么通过软中断实现调用系统调用clone函数,而clone函数的精华copy_process函数就在此篇文章中进行分析。我们知道,在linux系统中,应用层可以创建子进程和子线程(轻量级进程)两种程序分支结构。而对于linux内核而且,并不详细区分子进程和子线程(轻量级进程)的区别,他们都使用的是task_struct结构(此结构极其复杂,包含非常多的数据结构),而不同的是子进程和子线程的task_struct初始化结果不同。task_struct结构是一个进程或线程的标识和存在的凭证,调度程序就是通过task_struct结构来区分不同的进程(线程)。里面包含了进程(线程)所有需要用到的结构(内存描述符,文件描述符,信号描述符,信号处理函数,调度优先级等)。而我们知道,一个进程(线程)不止有自己的task_struck结构,还必须有一个自己的内核栈,当执行进程切换时,部分进程上下文会保存于其进程的内核栈中,而中断发生时的中断上下文也会保存于正在持续的进程内核栈中。在copy_process函数中内核栈的初始化导致了fork()的两次返回值不同(之后会说明)。当然,copy_process还涉及到许多操作,比如新进程(线程)的安全检测,pid的分配,关系调整(父子进程、进程组关系,命名空间关系等),内存结构的初始化等等,这些我们在之后的代码中慢慢道来。
copy_process
/* 代码目录:linux源码/kernel/Fork.c */ static struct task_struct *copy_process(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *child_tidptr,
struct pid *pid,
int trace)
{
int retval;
struct task_struct *p; /* CLONE_FS 不能与 CLONE_NEWNS 或 CLONE_NEWUSER 同时设置 */
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL); if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
return ERR_PTR(-EINVAL); /* 创建线程时线程之间要共享信号处理函数 */
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL); /*
* 父子进程共享信号处理函数时必须共享内存地址空间
* 这就是为什么书上写的fork出来的父子进程有其独立的信号处理函数,因为他们的内存地址空间不同
*/
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL); /*
* 防止参数init进程的兄弟进程
* 只有init进程的 signal->flags & SIGNAL_UNKILLABLE 为真
* 因为当进程退出时实际上是成为了僵尸进程(zombie),而要通过init进程将它回收,而如果此进程为init的兄弟进程,则没办法将其回收
*/
if ((clone_flags & CLONE_PARENT) &&
current->signal->flags & SIGNAL_UNKILLABLE)
return ERR_PTR(-EINVAL); /* 如果新的进程将会有新的用户空间或者pid,则不能让它共享父进程的线程组或者信号处理或者父进程 */
if (clone_flags & CLONE_SIGHAND) {
if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
(task_active_pid_ns(current) !=
current->nsproxy->pid_ns_for_children))
return ERR_PTR(-EINVAL);
} /* 附加安全检查 */
retval = security_task_create(clone_flags);
if (retval)
goto fork_out; retval = -ENOMEM;
/* 为新进程分配struct task_struct内存和内核栈内存 */
p = dup_task_struct(current);
if (!p)
goto fork_out; /* ftrace是用于内核性能分析和跟踪的 */
ftrace_graph_init_task(p); /* futex初始化,其用于SYSTEM V IPC,具体可见 http://blog.chinaunix.net/uid-7295895-id-3011238.html */
rt_mutex_init_task(p); #ifdef CONFIG_PROVE_LOCKING
DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
#endif
retval = -EAGAIN;
/* 检查 tsk->signal->rlim[RLIMIT_NPROC].rlim_cur是否小于等于用户所拥有的进程数,rlim结构体表示相关资源的最大值 */
if (atomic_read(&p->real_cred->user->processes) >= task_rlimit(p, RLIMIT_NPROC)) {
/* INIT_USER是root权限。检查父进程是否有root权限 */
if (p->real_cred->user != INIT_USER && !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
goto bad_fork_free;
}
current->flags &= ~PF_NPROC_EXCEEDED; /* 将父进程的cred复制到子进程的real_cred和cred。struct cred用于安全操作的结构 */
retval = copy_creds(p, clone_flags);
if (retval < )
goto bad_fork_free; retval = -EAGAIN;
/* 进程数量是否超出系统允许最大进程数量,最大进程数量跟内存有关,一般原则是所有的进程内核栈(默认8K)加起来不超过总内存的1/8,可通过/proc/sys/kernel/threads-max改写此值 */
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count; /* 如果实现新进程的执行域和可执行格式的内核函数都包含在内核模块中,则递增其使用计数 */
if (!try_module_get(task_thread_info(p)->exec_domain->module))
goto bad_fork_cleanup_count; delayacct_tsk_init(p); /* Must remain after dup_task_struct() */ /* 清除 PF_SUPERPRIV(表示进程使用了超级用户权限) 和 PF_WQ_WORKER(使用了工作队列) */
p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
/* 设置 PF_FORKNOEXEC 表明此子进程还没有进行 execve() 系统调用 */
p->flags |= PF_FORKNOEXEC; /* 初始化子进程的子进程链表和兄弟进程链表为空 */
INIT_LIST_HEAD(&p->children);
INIT_LIST_HEAD(&p->sibling);
/* 见 http://www.ibm.com/developerworks/cn/linux/l-rcu/ */
rcu_copy_process(p);
p->vfork_done = NULL;
/* 初始化分配锁,此锁用于保护分配内存,文件,文件系统等操作 */
spin_lock_init(&p->alloc_lock); /* 信号列表初始化,此列表保存被挂起的信号 */
init_sigpending(&p->pending); /* 代码执行时间变量都置为0 */
p->utime = p->stime = p->gtime = ;
p->utimescaled = p->stimescaled = ;
#ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
p->prev_cputime.utime = p->prev_cputime.stime = ;
#endif
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
seqlock_init(&p->vtime_seqlock);
p->vtime_snap = ;
p->vtime_snap_whence = VTIME_SLEEPING;
#endif #if defined(SPLIT_RSS_COUNTING)
memset(&p->rss_stat, , sizeof(p->rss_stat));
#endif
/* 此变量一般用于epoll和select,从父进程复制过来 */
p->default_timer_slack_ns = current->timer_slack_ns; /* 初始化进程IO计数结构 */
task_io_accounting_init(&p->ioac);
acct_clear_integrals(p); /* 初始化cputime_expires结构 */
posix_cpu_timers_init(p); /* 设置进程创建时间 */
p->start_time = ktime_get_ns();
p->real_start_time = ktime_get_boot_ns(); /* io_context 和 audit_context 置空 */
p->io_context = NULL;
p->audit_context = NULL;
/* 如果创建的是线程,因为需要修改到当前进程的描述符,会先上锁 */
if (clone_flags & CLONE_THREAD)
threadgroup_change_begin(current);
cgroup_fork(p);
#ifdef CONFIG_NUMA
p->mempolicy = mpol_dup(p->mempolicy);
if (IS_ERR(p->mempolicy)) {
retval = PTR_ERR(p->mempolicy);
p->mempolicy = NULL;
goto bad_fork_cleanup_threadgroup_lock;
}
#endif
#ifdef CONFIG_CPUSETS
p->cpuset_mem_spread_rotor = NUMA_NO_NODE;
p->cpuset_slab_spread_rotor = NUMA_NO_NODE;
seqcount_init(&p->mems_allowed_seq);
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
p->irq_events = ;
p->hardirqs_enabled = ;
p->hardirq_enable_ip = ;
p->hardirq_enable_event = ;
p->hardirq_disable_ip = _THIS_IP_;
p->hardirq_disable_event = ;
p->softirqs_enabled = ;
p->softirq_enable_ip = _THIS_IP_;
p->softirq_enable_event = ;
p->softirq_disable_ip = ;
p->softirq_disable_event = ;
p->hardirq_context = ;
p->softirq_context = ;
#endif
#ifdef CONFIG_LOCKDEP
p->lockdep_depth = ; /* no locks held yet */
p->curr_chain_key = ;
p->lockdep_recursion = ;
#endif #ifdef CONFIG_DEBUG_MUTEXES
p->blocked_on = NULL; /* not blocked yet */
#endif
#ifdef CONFIG_BCACHE
p->sequential_io = ;
p->sequential_io_avg = ;
#endif /* 初始化子进程的调度优先级和策略,在此并没有将此进程加入到运行队列,在copy_process返回之后加入 */
retval = sched_fork(clone_flags, p);
if (retval)
goto bad_fork_cleanup_policy; /* perf event是一个性能调优工具,具体见 http://blog.sina.com.cn/s/blog_98822316010122ex.html */
retval = perf_event_init_task(p);
if (retval)
goto bad_fork_cleanup_policy;
retval = audit_alloc(p);
if (retval)
goto bad_fork_cleanup_perf;
/* 初始化 p->sysvshm.shm_clist 链表头 */
shm_init_task(p); /* copy_semundo, copy_files, copy_fs, copy_sighand, copy_signal, copy_mm, copy_namespaces, copy_io都是根据clone_flags从父进程做相应的复制 */
retval = copy_semundo(clone_flags, p);
if (retval)
goto bad_fork_cleanup_audit;
retval = copy_files(clone_flags, p);
if (retval)
goto bad_fork_cleanup_semundo;
retval = copy_fs(clone_flags, p);
if (retval)
goto bad_fork_cleanup_files;
/* 判断是否设置 CLONE_SIGHAND ,如果是(线程必须为是),增加父进行的sighand引用计数,如果否(创建的必定是子进程),将父线程的sighand_struct复制到子进程中 */
retval = copy_sighand(clone_flags, p);
if (retval)
goto bad_fork_cleanup_fs;
/* 如果创建的是线程,直接返回0,如果创建的是进程,则会将父进程的信号屏蔽和安排复制到子进程中 */
retval = copy_signal(clone_flags, p);
if (retval)
goto bad_fork_cleanup_sighand;
/*
* 如果是进程,则将父进程的mm_struct结构复制到子进程中,然后修改当中属于子进程有别于父进程的信息(如页目录)
* 如果是线程,则将子线程的mm指针和active_mm指针都指向父进程的mm指针所指结构。
*/
retval = copy_mm(clone_flags, p);
if (retval)
goto bad_fork_cleanup_signal;
retval = copy_namespaces(clone_flags, p);
if (retval)
goto bad_fork_cleanup_mm;
retval = copy_io(clone_flags, p);
if (retval)
goto bad_fork_cleanup_namespaces; /*
* 初始化子进程内核栈和thread_struct结构体
* 当进程切换时,进程的硬件上下文一般保存于三个地方: tss_struct(保存进程内核栈地址,I/O许可权限位),thread_struct(大部分非通用寄存器),进程内核栈(通用寄存器)
* copy_thread函数会将父进程的thread_struct和内核栈数据复制到子进程中,并将子进程的返回值置为0(x86返回值保存在eax中,arm保存在r0中,即把eax或者r0所在的内核栈数据置为0)
* copy_thread函数还会将子进程的eip寄存器值设置为ret_from_fork()的地址,即当子进程首次被调用就立即执行系统调用clone返回。
* 所以应用层调用fork()函数后,子进程返回0,父进程返回子进程ID(返回子进程ID在之后代码中会实现)
*/
retval = copy_thread(clone_flags, stack_start, stack_size, p);
if (retval)
goto bad_fork_cleanup_io; /* 判断是不是init进程 */
if (pid != &init_struct_pid) {
retval = -ENOMEM;
/* 分配pid */
pid = alloc_pid(p->nsproxy->pid_ns_for_children);
if (!pid)
goto bad_fork_cleanup_io;
} /* 如果设置了CLONE_CHILD_SETTID则将task_struct中的set_child_tid指向用户空间的child_tidptr,否则置空 */
p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
/* 如果设置了CLONE_CHILD_CLEARTID则将task_struct中的clear_child_tid指向用户空间的child_tidptr,否则置空 */
p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr : NULL; #ifdef CONFIG_BLOCK
p->plug = NULL;
#endif
#ifdef CONFIG_FUTEX
p->robust_list = NULL;
#ifdef CONFIG_COMPAT
p->compat_robust_list = NULL;
#endif
INIT_LIST_HEAD(&p->pi_state_list);
p->pi_state_cache = NULL;
#endif
/*
* 如果共享VM或者vfork创建,信号栈清空
*/
if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM)
p->sas_ss_sp = p->sas_ss_size = ; /*
* 系统调用跟踪时应该禁止单步执行
*/
user_disable_single_step(p);
clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
#ifdef TIF_SYSCALL_EMU
clear_tsk_thread_flag(p, TIF_SYSCALL_EMU);
#endif
clear_all_latency_tracing(p); /* 将子进程的PID设置为分配的PID在全局namespace中分配的值,在不同namespace中进程的PID不同,而p->pid保存的是全局的namespace中所分配的PID */
p->pid = pid_nr(pid);
if (clone_flags & CLONE_THREAD) {
/* 创建的是线程 */
p->exit_signal = -;
/* 线程组的所有线程的group_leader都一致 */
p->group_leader = current->group_leader;
/* 线程组的所有线程的tgid都一致,使用getpid返回的就是tgid */
p->tgid = current->tgid;
} else {
/* 创建的是子进程 */
if (clone_flags & CLONE_PARENT)
p->exit_signal = current->group_leader->exit_signal;
else
p->exit_signal = (clone_flags & CSIGNAL);
p->group_leader = p;
/* tgid与pid一致,所以当创建子线程时,tgid与主线程的一致 */
p->tgid = p->pid;
} /* 初始化页框中脏页数量为0 */
p->nr_dirtied = ;
/* 初始化脏页数量临界值,当脏页数量到达临界值时,会调用balance_dirty_pages()将脏页写入磁盘 */
p->nr_dirtied_pause = >> (PAGE_SHIFT - );
/* 将脏页写入磁盘的开始时间 */
p->dirty_paused_when = ; p->pdeath_signal = ;
/* 初始化线程组链表为空 */
INIT_LIST_HEAD(&p->thread_group);
p->task_works = NULL; /* 到此系统中已经存在此进程(线程),但是它还不能够执行,需要等待父进程对其处理,这里会上锁 */
write_lock_irq(&tasklist_lock); if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
/* 创建的是兄弟进程或者相同线程组线程 */
/* 其父进程为父进程的父进程 */
p->real_parent = current->real_parent;
/* 其父进程执行域为父进程的父进程执行域 */
p->parent_exec_id = current->parent_exec_id;
} else {
/* 创建的是子进程 */
/* 父进程为父进程 */
p->real_parent = current;
/* 父进程的执行域为父进程的执行域 */
p->parent_exec_id = current->self_exec_id;
} /* 当前进程信号处理上锁,这里应该是禁止了信号处理 */
spin_lock(¤t->sighand->siglock); /*
* seccomp与系统安全有关,具体见 http://note.sdo.com/u/634687868481358385/NoteContent/M5cEN~kkf9BFnM4og00239
*/
copy_seccomp(p); /*
* 在fork之前,进程组和会话信号都需要送到父亲结点,而在fork之后,这些信号需要送到父亲和孩子结点。
* 如果我们在将新进程添加到进程组的过程中出现一个信号,而这个挂起信号会导致当前进程退出(current),我们的子进程就不能够被kill或者退出了
* 所以这里要检测父进程有没有信号被挂起。
*/
recalc_sigpending();
if (signal_pending(current)) {
/* 包含有挂起进程,错误 */
spin_unlock(¤t->sighand->siglock);
write_unlock_irq(&tasklist_lock);
retval = -ERESTARTNOINTR;
goto bad_fork_free_pid;
} if (likely(p->pid)) {
/* 如果子进程需要跟踪,就将 current->parent 赋值给 tsk->parent ,并将子进程插入调试程序的跟踪链表中 */
ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace); /* p->pids[PIDTYPE_PID].pid = pid; */
init_task_pid(p, PIDTYPE_PID, pid); /* 如果是子进程(其实就是判断 p->exit_signal 是否大于等于0,创建的是线程的话,exit_signal的值为-1) */
if (thread_group_leader(p)) {
/* p->pids[PIDTYPE_PGID].pid = current->group_leader->pids[PIDTYPE_PGID].pid; PGID为进程组ID,所以直接复制父进程的pgid */
init_task_pid(p, PIDTYPE_PGID, task_pgrp(current));
/* p->pids[PIDTYPE_SID].pid = current->group_leader->pids[PIDTYPE_SID].pid; SID为会话组ID,当没有使用setsid()时,子进程的sid与父进程一致 */
init_task_pid(p, PIDTYPE_SID, task_session(current)); /* return pid->numbers[pid->level].nr == 1; 判断新进程是否处于一个新创建的namespace中(新进程所在的新namespace中的pid会为1,以此判断) */
if (is_child_reaper(pid)) {
/* 将当前namespace的init进程设置为此新进程 */
ns_of_pid(pid)->child_reaper = p;
p->signal->flags |= SIGNAL_UNKILLABLE;
} p->signal->leader_pid = pid;
p->signal->tty = tty_kref_get(current->signal->tty); /* 将此进程添加到父进程的子进程链表 */
list_add_tail(&p->sibling, &p->real_parent->children);
/* 将此进程task_struct加入到task链表中 */
list_add_tail_rcu(&p->tasks, &init_task.tasks);
/* 将新进程描述符的pgid结构插入pgid_hash */
attach_pid(p, PIDTYPE_PGID);
/* 将新进程描述符的sid结构插入sid_hash */
attach_pid(p, PIDTYPE_SID);
/* 当前cpu进程数量加1 */
__this_cpu_inc(process_counts);
} else {
/* 创建的是线程,这里的处理导致了线程会共享信号 */
current->signal->nr_threads++;
atomic_inc(¤t->signal->live);
atomic_inc(¤t->signal->sigcnt);
/* 将新线程的thread_group结点加入到线程组的领头线程的thread_group链表中 */
list_add_tail_rcu(&p->thread_group,
&p->group_leader->thread_group);
/* 将新线程的thread_node结点加入的新线程的signal->thread_head中 */
list_add_tail_rcu(&p->thread_node,
&p->signal->thread_head);
}
/* 将新进程描述符的pid结构插入pid_hash */
attach_pid(p, PIDTYPE_PID);
/* 当前系统进程数加1 */
nr_threads++;
} /* 已创建的进程数量加1 */
total_forks++;
/* 释放当前进程信号处理锁 */
spin_unlock(¤t->sighand->siglock);
syscall_tracepoint_update(p);
/* 释放tasklist_lock锁 */
write_unlock_irq(&tasklist_lock); /* 将新进程与proc文件系统进行关联 */
proc_fork_connector(p);
cgroup_post_fork(p);
/* 如果创建的是线程,释放此锁 */
if (clone_flags & CLONE_THREAD)
threadgroup_change_end(current);
perf_event_fork(p); trace_task_newtask(p, clone_flags);
uprobe_copy_process(p, clone_flags); /* 返回新进程的task_struct结构 */
return p; /* 以下为执行期间的错误处理 */
bad_fork_free_pid:
if (pid != &init_struct_pid)
free_pid(pid);
bad_fork_cleanup_io:
if (p->io_context)
exit_io_context(p);
bad_fork_cleanup_namespaces:
exit_task_namespaces(p);
bad_fork_cleanup_mm:
if (p->mm)
mmput(p->mm);
bad_fork_cleanup_signal:
if (!(clone_flags & CLONE_THREAD))
free_signal_struct(p->signal);
bad_fork_cleanup_sighand:
__cleanup_sighand(p->sighand);
bad_fork_cleanup_fs:
exit_fs(p); /* blocking */
bad_fork_cleanup_files:
exit_files(p); /* blocking */
bad_fork_cleanup_semundo:
exit_sem(p);
bad_fork_cleanup_audit:
audit_free(p);
bad_fork_cleanup_perf:
perf_event_free_task(p);
bad_fork_cleanup_policy:
#ifdef CONFIG_NUMA
mpol_put(p->mempolicy);
bad_fork_cleanup_threadgroup_lock:
#endif
if (clone_flags & CLONE_THREAD)
threadgroup_change_end(current);
delayacct_tsk_free(p);
module_put(task_thread_info(p)->exec_domain->module);
bad_fork_cleanup_count:
atomic_dec(&p->cred->user->processes);
exit_creds(p);
bad_fork_free:
free_task(p);
fork_out:
return ERR_PTR(retval);
}
流程图
小结
copy_process作为do_fork的主心骨,其流程并不复杂,只是每一步调用的初始化函数都非常精妙,涉及到大量的内知识和代码,这里为了篇幅着想就不继续往细节分析了,会在之后的文章中慢慢补全其中的知识和自己的理解。整篇文章读下来,其实copy_process的核心就是初始化task_struct结构体供新进程(线程)使用,并为其分配独有的pid,最后将其加入到运行队列中。而至于为什么应用层调用fork()会进行两次返回,原理就是在内核栈中,在copy_thread函数中父进程将其内核栈复制到子进程中,把子进程被调度后执行的第一条语句设置为do_fork()返回,并把保存返回值的寄存器值(一般返回值保存在eax(ARM是r0),而这些通用寄存器值保存在内核栈中,当父进程调用后copy_thread函数会进行进程切换,会把这些保存于内核栈的寄存器值还原到寄存器中)置为0,所以子进程的返回值为0,而父进程会继续执行copy_thread函数之后的初始化,最后返回子进程的pid(实际上是tgid)。
线程独占和共享的资源
线程独有的内容包括:
- PID(进程的所有子线程的tgid是一样的)
- 寄存器组的值
- 线程的栈(包括内核栈)
- 错误返回码
- 线程的信号屏蔽码
线程与父进程共享的内容包括:
- 代码段(用户线程的整个mm_struct指向父进程的mm_struct, 而内核线程的mm_struct为NULL, 会使用run_queue->prev_mm, 也就是前一个切换出去的进程的mm_struct, 所以也有了lazy_tlb, 也就不需要使tlb无效)
- 进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)
- 打开的文件描述符
- 信号的处理函数
- 进程的当前目录
- 进程用户 ID 与进程组 ID
关于linux系统如何实现fork的研究(二)的更多相关文章
- 关于linux系统如何实现fork的研究(二)【转】
转自:http://www.aichengxu.com/linux/7166015.htm 本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 前一篇关于li ...
- 关于linux系统如何实现fork的研究(一)
引言 fork函数是用于在linux系统中创建进程所使用,而最近看了看一个fork()调用是怎么从应用到glibc,最后到内核中实现的,这片文章就聊聊最近对这方面研究的收获吧.我们主要聊聊从g ...
- 关于linux系统如何实现fork的研究(一)【转】
转自:http://www.aichengxu.com/linux/4157180.htm 引言 fork函数是用于在linux系统中创建进程所使用,而最近看了看一个fork()调用是怎么从应用到gl ...
- 基于Linux系统WINE虚拟机技术的研究
650) this.width=650;" onclick="window.open("http://blog.51cto.com/viewpic.php?refimg= ...
- Linux系统编程-----进程fork()
在开始之前,我们先来了解一些基本的概念: 1. 程序, 没有在运行的可执行文件 进程, 运行中的程序 2. 进程调度的方法: 按时间片轮转 先来先服务 短时间优先 按优先级别 3. 进程的状态: 就绪 ...
- 【转】LINUX系统I/O复用技术之二:poll() -- 不错
原文网址:http://www.cnblogs.com/alyssaCui/archive/2013/04/01/2993886.html poll poll或select为大部分Unix/Linux ...
- Linux From Scratch(从零开始构建Linux系统,简称LFS)(二)
七. 构建临时系统 1. 通用编译指南 a. 确认是否正确设置了 LFS 环境变量 echo $LFS b. 假定你已经正确地设置了宿主系统需求和符号链接 c. 对于每个软件包: (1). 确保解压软 ...
- linux系统编程之文件与io(二)
今天继续学习文件与io,话不多说,开始进入正题: 文件的read和write系统调用: 说明:函数中出现在size_t和ssize_t是针对系统定制的数据类型: 下面以一个实现文件简单拷贝的示 ...
- Linux系统调优——Memory内存(二)
(1).查看Memory(内存)运行状态相关工具 1)free命令查看内存使用情况 [root@youxi1 ~]# free -m //-m选项,以MB为单位显示 total used free s ...
随机推荐
- Linux shell用法和技巧(转)
原文出处: techbar 译文出处: 外刊IT评论 使用Linux shell是我每天的基本工作,但我经常会忘记一些有用的shell命令和技巧.当然,命令我能记住,但我不敢说能记得如何用它执行某 ...
- WPF学习03:Element Binding
元素绑定是数据绑定的一个子集,关于数据绑定是什么,园子里有很多文章都说得非常好,在此不予详细说明. WPF实现了完善的数据绑定机制,使得元素绑定有简易的实现步骤. 本文的元素指的是WPF中的可视控件. ...
- WPF Canvas 画区域
有时候需要实现类似于QQ截图那样的选择区域功能,这里的区域可以是一条线,圆,矩形等等 实现原理就是一个Canvas做蒙板,然后canvas的三个事件,MouseLeftButtonDown,Mouse ...
- Excle快速输入√与×
如何在EXCLE中快速输入√与×呢 很简单的一个小技巧,只需要在EXCLE单元格中输入P(O) 然后将其字体设置为Wingdings 2,接着就出现符号了 不信你可以试试的哦.
- eclipse+cdt+minGW (C/C++ 编译)
1. 安装Eclipse CDT 方法1: 已安装Eclipse的话,可以通过菜单Help->Install New Software,安装CDT插件. 点击ADD后 Name:CDT L ...
- Windows Phone性能优化建议
使用background thread解码图片 在Windows Phone中支持的图片格式有jpg和png,微软建议使用jpg格式的图片,因为jpg格式的图片在解码速度上要比png快.那么我们怎么来 ...
- HTML5简略介绍
今天要说下 HTML5特有的一个元素 canvas ,旨在让web页面上作矢量图不需要在依靠flash或是其他插件,在网页上使用canvas元素时,它会创建一块矩形区域,默认300*150,当然也是可 ...
- Swift 中的利刃,函数和闭包
input[type="date"].form-control,.input-group-sm>input[type="date"].input-grou ...
- 嗨分享-前端技术-帝国CMS手机站修改列表分页(sysShowListMorePage)
http://bbs.phome.net/showthread-31-318753-0.html 如果你的网站使用的是帝国CMS.PC站和手机站各使用一个模板组,但共同使用一个数据库.那么你的PC站和 ...
- INPC & RaizePropertyChanged in mvvmlight
INPC & RaizePropertyChanged in mvvmlight In WPF app, MvvM Framework, we binding the UIElement fr ...