文章目录
1. 背景描述
1.1 多进程或者多线程?
我们先想想多进程多线程的特点:
多进程特点:
每个进程独立运行,一个进程挂死,不影响其他进程的运行;
虚拟内存隔离,内存安全。
需要增加进程切换的开销。
多线程特点:
内存无法进行隔离,一个线程挂死,整个程序挂死;
但是线程切换的开销相对进程低;
在此特点的基础上,回答为何需要多进程多线程,当然是我们需要多进程与多线程的优点,Linux是一个多进程的操作系统,同时也支持多线程。
1.2.freertos uCOS等系统与Linux系统之间的区别?
比如stm32系列可以运行freertos uCOS等内核,此系统也可以内存管理、任务调度等功能,但是最大的区别是不能运行多进程的程序,因为它没有mmu,不支持多进程的切换。
1.3.多进程与mmu?
考虑下这个问题,多进程是通过什么进行切换的,通过mmu控制器来进行切换,正是因为有了mmu,才能运行linux之类的多进程操作系统,才会有虚拟地址的概念。
2.Linux进程描述之task_struct
进程与线程在Linux下都是通过task_struct的结构体来进行描述的,仅有的不同的是资源分配的不同而已,结构体比较大,下面只截取关键的部分进行说明;
2.1 进程状态
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
进程的基本状态,内核中描述进程的状态:
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
/* in tsk->state again */
#define TASK_DEAD 64
#define TASK_WAKEKILL 128
#define TASK_WAKING 256
#define TASK_STATE_MAX 512
/* Convenience macros for the sake of set_task_state */
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED (TASK_WAKEKILL | __TASK_TRACED)
/* Convenience macros for the sake of wake_up */
#define TASK_NORMAL (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
#define TASK_ALL (TASK_NORMAL | __TASK_STOPPED | __TASK_TRACED)
/* get_task_state() */
#define TASK_REPORT (TASK_RUNNING | TASK_INTERRUPTIBLE | \
TASK_UNINTERRUPTIBLE | __TASK_STOPPED | \
__TASK_TRACED)
flags反应进程的状态信息,用于内核识别当前进程的状态;
ptrace提供了一种父进程,它可以被用来控制子进程,常被用来进行断点调试;
2.2 进程调度字段
int prio, static_prio, normal_prio;
unsigned int rt_priority;
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt;
包含进程优先级,调度类sched_class,以及调度实体sched_entity、 sched_rt_entity 等;
2.3 内存字段
struct mm_struct *mm, *active_mm;
#ifdef CONFIG_COMPAT_BRK
unsigned brk_randomized:1;
#endif
#if defined(SPLIT_RSS_COUNTING)
struct task_rss_stat rss_stat;
#endif
进程的内存空间描述符等;
2.4 进程关系的字段
/*
* pointers to (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->real_parent->pid)
*/
struct task_struct *real_parent; /* real parent process */
struct task_struct *parent; /* recipient of SIGCHLD, wait4() reports */
/*
* children/sibling forms the list of my natural children
*/
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
struct task_struct *group_leader; /* threadgroup leader */
/*
* ptraced is the list of tasks this task is using ptrace on.
* This includes both natural children and PTRACE_ATTACH targets.
* p->ptrace_entry is p's link on the p->parent->ptraced list.
*/
struct list_head ptraced;
struct list_head ptrace_entry;
包括父进程、子进程以及进程组的领头进程等;
2.5 资源字段
/* CPU-specific state of this task */
struct thread_struct thread;
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;
/* namespaces */
struct nsproxy *nsproxy;
/* signal handlers */
struct signal_struct *signal;
struct sighand_struct *sighand;
包括文件描述符、打开的文件、信号等参数;
3.进程与线程的创建
task_struct描述了对资源的分配以及进程的状态信息等;
进程与线程的创建函数:fork vfork clone pthread_create kernel_thread,前四个是用户态的函数,kernel_thread是内核态创建线程所使用的;
3.1 fork vfork clone pthread_create函数区别于联系
以上函数实现皆在c库里面进行实现,即glibc中实现,查看glibc源码即可知,以上都是调用了clone函数,然后通过sys_clone的系统调用进行实现,然后通过do_fork函数进行进程或者线程的实际创建。
可以看见,用户态是通过系统调用进入内核,然后最终调用到do_fork函数进行进程或者线程的创建,也更能说明进程与线程的关系在Linux中其实是类似的;
为何没有通过fork直接系统调用,此问题可以参考http://cn.voidcc.com/question/p-rxufpyok-bhk.html
3.2 kernel_thread函数
以上四个接口都是用户态的函数:内核态也有一个函数接口kernel_thread,此函数用于内核态的线程创建,
//arch/arm/kernel/process.c
/*
* Create a kernel thread.
*/
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
struct pt_regs regs;
memset(®s, 0, sizeof(regs));
regs.ARM_r4 = (unsigned long)arg;
regs.ARM_r5 = (unsigned long)fn;
regs.ARM_r6 = (unsigned long)kernel_thread_exit;
regs.ARM_r7 = SVC_MODE | PSR_ENDSTATE | PSR_ISETSTATE;
regs.ARM_pc = (unsigned long)kernel_thread_helper;
regs.ARM_cpsr = regs.ARM_r7 | PSR_I_BIT;
return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
}
内核的线程创建,最终还是调用的函数do_fork函数,仅有的差异也只是标志的差异;
3.3 clone_flags
fork vfork clone pthread_create kernel_thread最大的差异也就是clone_flags,此标志控制着资源的分配以及是否继承父进程/线程等信息;
从下表也可以看出fork 、vfork、pthread_create调用了clone的系统调用也是容易理解的,因为clone可以方便用户传参,而其余几个glibc中已经规定好了参数;
内核的clone_flag的定义
//include/linux/sched.h
/*
* cloning flags:
*/
#define CSIGNAL 0x000000ff /* signal mask to be sent at exit */
#define CLONE_VM 0x00000100 /* set if VM shared between processes */
#define CLONE_FS 0x00000200 /* set if fs info shared between processes */
#define CLONE_FILES 0x00000400 /* set if open files shared between processes */
#define CLONE_SIGHAND 0x00000800 /* set if signal handlers and blocked signals shared */
#define CLONE_PTRACE 0x00002000 /* set if we want to let tracing continue on the child too */
#define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */
#define CLONE_THREAD 0x00010000 /* Same thread group? */
#define CLONE_NEWNS 0x00020000 /* New namespace group? */
#define CLONE_SYSVSEM 0x00040000 /* share system V SEM_UNDO semantics */
#define CLONE_SETTLS 0x00080000 /* create a new TLS for the child */
#define CLONE_PARENT_SETTID 0x00100000 /* set the TID in the parent */
#define CLONE_CHILD_CLEARTID 0x00200000 /* clear the TID in the child */
#define CLONE_DETACHED 0x00400000 /* Unused, ignored */
#define CLONE_UNTRACED 0x00800000 /* set if the tracing process can't force CLONE_PTRACE on this clone */
#define CLONE_CHILD_SETTID 0x01000000 /* set the TID in the child */
/* 0x02000000 was previously the unused CLONE_STOPPED (Start in stopped state)
and is now available for re-use. */
#define CLONE_NEWUTS 0x04000000 /* New utsname group? */
#define CLONE_NEWIPC 0x08000000 /* New ipcs */
#define CLONE_NEWUSER 0x10000000 /* New user namespace */
#define CLONE_NEWPID 0x20000000 /* New pid namespace */
#define CLONE_NEWNET 0x40000000 /* New network namespace */
#define CLONE_IO 0x80000000 /* Clone io context */