当前位置: 首页 > news >正文

乐清外贸网站建设软件系统开发平台

乐清外贸网站建设,软件系统开发平台,wordpress如何更改页面链接地址,python 做网站 套件介绍一个程序从源文件到进程执行的过程 1、编译链接#xff08;源文件到二进制文件#xff09; Linux 下面二进制的程序也要有严格的格式#xff0c;称为ELF#xff08;Executeable and Linkable Format#xff0c;可执行与可链接格式#xff09; #xff0c;这个格式可…介绍一个程序从源文件到进程执行的过程 1、编译链接源文件到二进制文件 Linux 下面二进制的程序也要有严格的格式称为ELFExecuteable and Linkable Format可执行与可链接格式 这个格式可以根据编译结果的不同分为不同的格式。 这里要复习一下静态链接和动态链接的特点以及整个过程。 静态库是编译的时候就加入到程序中优点是运行快发布时无需提供库文件缺点是程序大更新比较麻烦 动态库是运行时动态调用的优点是小多个程序公用节省内存缺点是慢发布需要提供库。 静态库制作和使用 gcc 源文件 -c :生成可重定位文件 ar rcs xx.a xx.o 通过ar工具生成静态库 使用 gcc main.c -lxx -L 路径 其中动态库要去掉lib前缀和.a后缀 动态库 gcc -shared -fpic xx.c -o libxx.so 使用和静态库一样 fpic用于生成位置无关的代码。 整体预处理宏展开删空格、编译.s的汇编文件、汇编.o的可重定位文件、链接链接多个.o文件以库文件组成可执行文件或者动态库 gcc不加参数直接生成可执行文件 -o是修改生成可执行文件的名字-c代表只生成可重定位的.o文件 主要有ELF头以及元数据的信息多少节偏移等代码区全局变量区只读数据区符号表等以及rel重定位的节 没有局部变量的。局部变量是存放在栈里面的是程序运行过程中随时分配空间随时释放的 下面是连接后的可执行文件 这个格式和.o 相似还是分成一个个的 section 并且被节头表描述只不过这些section 是多个.o 文件合并过的。因为这些section被分成了需要加载到内存里面的代码段、数据段和不需要加载到内存里面的部分符号表字符表元数据等。 将小的section 合成了大的段 segment 并且再最前面加了一个段头表。这里面除了有对于段的描述之外最重要的是p_vaddr这个是这个段加载到内存的虚拟地址。 动态链接库就是ELF 的第三种类型共享对象文件。ELF 文件中还多了两个section 一个是 .plt 过程连接表Procedure Linkage Table ,PLT 一个是.got.plt 全局偏移量表Global Offset Table GOT 内存的程序如何变成进程 对于ELF的二进制文件是如何加载到内存执行的呢 学过了系统调用一节你会发现原理是exec 这个系统调用最终调用的 load_elf_binary。 所以实际上就是execv用户层面-内核的execv函数-do-execv函数- load_elf_binary函数。通过用户到内核的系统调用把可执行文件加载到进程的内存中执行。 pe -ef可以查看进程情况 进程树 系统启动的 init 进程就是祖宗进程。祖宗进程会通过kernel_thread函数生成1号和2号进程。 2号进程kthreadd是负责内核进程调度管理的。 1号进程就是负责用户第一个进程的创建涉及到内核态到用户态的转换以及根文件系统的挂载1 号进程是 /sbin/init 如果在centos7里面查看是软连接到 systemd 的。 1号进程会启动很多的 daemon 进程为系统运行提供服务比如文件系统驱动周期性任务等然后就是启动getty让用户登录pts/0登录后运行 shell用户启动的进程都是通过shell 运行的从而行程一颗进程树。 ps -ef 这个命令的父进程是bashbash的父进程是ptspts的父进程是 sshd。 tty 问号说明一般不是前台启动的是后台的服务。 用户态的不带中括号内核态的带中括号。 线程 为什么需要多线程编程 如果一个任务是可以拆解的或者CPU利用率不高的前后相关性没有很大关联就可以并行执行。比如IO密集型的任务 比如有个开发任务要开发200个页面可以拆分成10个任务每个任务负责10个页面并行开发完成之后再做一次整合比一次开发200个页面快很多。 可以用多个进程实现并发吗 不划算 立项成本。创建进程占用资源太多 关于进程线程协程切换的时间具体是多少也有一篇文章介绍进程创建要有PCB分配内存、文件等资源赋值到PCB比较耗时,然后插入PCB链表线程公用进程的地址空间只分配自己的栈以及一些寄存器耗时小线程前切换也比较方便 沟通成本。进程之间的通信需要再不同的内存空间传来传去无法共享 除了任务可以并行的需求外还需要将前台和后台的任务隔离开所以就需要创建额外的线程去处理后台的任务。 **关于多线程的知识可以看另外几篇的文章。**这里只是简单介绍一下 可以用pthread_xxx也可以用C11的std:thread; 将线程访问的数据分为三类 线程本地栈上的本地数据。 函数执行过程中的局部变量每个线程都有自己的栈空间栈的大小可以通过unlimit -a来查看。 为了避免线程之间的栈空间踩踏线程栈之间还有有小块区域用来隔离保护各自的栈空间一个线程踏入隔离区会引发段错误。 进程里共享的全局变量。全局变量在一个进程里是共享的如果两个线程一起修改就会有问题需要一种机制来保护他们。锁机制关于锁又有很多可以说的了 线程私有数据。不常用-thread_local.(虽然可以共同访问但是只会记录拥有者的修改 锁最常用的互斥锁自旋锁原子锁的概念区别看另一篇。注意C11里面提供了RAII形式的lock_guard和unique_lock。二者区别是后者更灵活可以延迟上锁和解锁时间有lock,unlock接口。而前者没有这些接口构造函数析构函数自动执行。代价是后者性能差一点。 以及条件变量配合使用。当有某种同步关系的时候不要让别人一直等着wait阻塞一下准备好了notify别人再执行。其中惊群现象可以用一个while解决即达到条件再判断一次是不是真的达到了。 一般用PAUSE不是while。 对于调度时间简单理解: 系统调用200ns级别只是用户态到内核态的切换开销比较小了 进程切换多任务操作系统进程切换必不可少。进程切换涉及到页表全局目录切换、TLB、内核态的堆栈切换、硬件上下文比较耗时的。以及还有缓存不热的间接开销。平均每次上下文切换耗时3.5us左右 对于线程从操作系统视角看调度上和进程没有什么区别都是在等待队列的双向链表里选择一个task_struct切到运行态而已。只不过轻量级进程和普通进程的区别是可以共享同一内存地址空间、代码段、全局变量、同一打开文件集合而已。 大约每次线程切换开销大约是3.8us左右。从上下文切换的耗时上来看Linux线程轻量级进程其实和进程差别不太大。 所以从代价来看多线程错进程切换代价差别不大主要的是进程持有的资源比线程大得多所以有多线程替代多进程了其他的以及多进程通信麻烦 对于协程切换只需要120ns,很快是进程的1/30在空间上协程初始化创建的时候为其分配的栈有2KB。而线程栈要比这个数字大的多可以通过ulimit 命令查看一般都在几兆作者的机器上是10M。如果对每个用户创建一个协程去处理100万并发用户请求只需要2G内存就够了。 实际上线程池IO多路复用比协程效果好得多线程池避免频繁地创建销毁线程协程的调度需要自己写而线程的调度是操作系统完成的肯定比我们写的好。协程主要的作用是同步写法异步性能。 再进一步因为我们实现的是有栈协程对称协程现在很多语言已经有自己的异步运行时了比如rust,go以及C20也有了关键字就是await,async, 这可以真正实现协程。go的协程调度已经是语言层面了rust是规定了最基本的上层根据需求自己写。如果是这种协程和线程池比应该是协程更胜一筹的。毕竟创建和切换都轻量。 进程数据结构 进程或者线程到了内核中**统一都叫任务**Task由一个统一的结构 task_struct 进行管理。 如何管理这些任务呢? 1/Linux 内核先弄一个链表将所有的task_struct 串起来。 2 task_struct 里面涉及到任务ID的有下面几个 pid_t pid tid; 进程id,线程id pid_t tgid; 线程组Id,就是进程的id struct task_struct *group_leader; 一个进程如果只有主线程pid是自己tgid 是自己group_leader 指向的还是自己 一个进程如果创建其他线程。线程有自己的pidtgid是进程的主线程的pidgroup_leader 指向的就是进程的主线程。有了 tgid我们就知道 tast_struct 代表的是一个进程还是一个线程 3、信号处理 task_struct就是一些信号处理的字段。 被阻塞暂不处理blocked 尚待处理pending 正在处理sighand 处理的结果忽略、结束进程 信号处理函数默认使用用户态的函数栈 4、任务状态 volatile long state; /* -1 unrunnable, 0 runnable, 0 stopped */ TASK_RUNNING 并不是说进程正在运行而是表示进程在时刻准备运行的状态。当处于这个状态的进程获得时间片的时候就是在运行中如果没有获得时间片就说明它被其他进程抢占了在等待再次分配时间片。相当于就绪状态。 在运行中的进程一旦要进行一些I/O的操作需要等待I/O完毕这个时候会释放CPU进入睡眠状态。在Linux中有两种睡眠状态 1、TASK_INTERRUPTIBLE (可中断的睡眠状态) 在浅睡眠等待I/O完成。 一个信号来的时候进程还是要被唤醒。唤醒后进行信号处理。 程序员可以根据自己的意愿来写处理函数例如收到某些信号放弃等待这个I/O操作完成直接退出或者收到某些消息继续等待。 2、TASK_UNINTERRUPTIBLE (不可中断的睡眠状态) 不可被信号唤醒只能死等I/O操作完成。 一旦I/O操作因为特殊原因不能完成这个时候谁也叫不醒这个进程。 kill 也是一个信号kill 信号也被忽略。 除非重启电脑没有其他办法。 因此这个是比较危险的事情不然不要把进程设置成 TASK_UNINTERRUPTIBLE TASK_STOPPED 是在进程接收到 SIGSTOPkill -19暂停进程相当于加了断点SIGSTP是尽可能停止可以捕获忽略、SIGTTINcrtlc前台进程终止信号可自定义信号处理函数、 SIGKILL(kill -9,不能忽略和重写sigterm kill-15可以捕获忽略)信号之后进入该状态。 TASK_TRACED进程被debugger 等进程监视进程执行被调试程序所停止。 当一个进程被另外的进程所监视每一个信号都会让进程进入该状态。 一旦一个进程要结束先进入的是EXIT_ZOMBIE状态但是这个时候它的父进程还没有使用 wait 等系统调用来获知它的终止信息此时进程就成了僵尸进程。 EXIT_DEAD 是进程的最终状态 僵尸进程和孤儿进程 正常情况下子进程由父进程创建子进程再创建新的进程。父子进程是一个异步过程父进程永远无法预测子进程的结束所以当子进程结束后它的父进程会调用wait()或waitpid()取得子进程的终止状态回收掉子进程的资源。 孤儿进程父进程结束了而它的一个或多个子进程还在运行那么这些子进程就成为孤儿进程(father died)。子进程的资源由init进程(进程号PID 1)回收。僵尸进程子进程退出了但是父进程没有用wait或waitpid去获取子进程的状态信息那么子进程的进程描述符仍然保存在系统中这种进程称为僵死进程。 危害 这种机制是在每个进程退出的时候内核会释放所有的资源包括打开的文件占用的内存等。但是仍保留一部分信息(进程号PID退出状态运行时间等)。直到父进程通过wait或waitpid来取时才释放。 但是这样就会产生问题如果父进程不调用wait或waitpid的话那么保留的信息就不会被释放其进程号就会被一直占用但是系统所能使用的进程号是有限的如果大量产生僵死进程将因没有可用的进程号而导致系统无法产生新的进程这就是僵尸进程的危害 孤儿进程是没有父进程的进程它由init进程循环的wait()回收资源init进程充当父进程。因此孤儿进程并没有什么危害。 补充任何一个子进程(init除外)在exit()之后并非马上就消失掉而是留下一个称为僵尸进程的数据结构进程号PID退出状态运行时间等待父进程去处理。如果父进程在子进程exit()之后没有及时处理出现僵尸进程并可以用ps命令去查看它的状态是“Z”。 为什么会产生Z进程当父进程没有读取到子进程的exit信号就会有僵尸进程。 解决办法 1、直接杀死僵尸进程的父进程这样就变成孤儿进程被init进程回收这样不好 2、父进程用wait,waitpid等待子进程结束这会导致父进程阻塞更好的是下面信号的方式 3、子进程死后会发送一个sigchd信号父进程只要signal(sigchild,sigign).表示不关心子进程的结束这样内核就会直接回收不再给父进程法信号了。 4、两次fork方法。子进程fork孙进程后立即退出孙子完成任务后就变成孤儿进程init回收。 进程调度相关 调度器优先级调度策略等等 这个图总结了task_struct的结构。主要包括了内存、文件管理相关的、内核栈、以及调度相关的比如状态信号处理及结果调度器优先级、统计信息运行时间开始时间等id。 继续说结构 运行统计信息 u64 utime;//用户态消耗的CPU时间 u64 stime;//内核态消耗的CPU时间 unsigned long nvcsw;//自愿(voluntary)上下文切换计数 unsigned long nivcsw;//非自愿(involuntary)上下文切换计数 u64 start_time;//进程启动时间不包含睡眠时间 u64 real_start_time;//进程启动时间包含睡眠时间 进程亲缘关系 parent 指向其父进程。当它终止时必须向它的父进程发送信号 children 表示链表的头部链表中的所有元素都是它的子进程 sibling 用于把当前进程插入到兄弟链表中 进程权限 uid gid euid egid rwxrwxrwx chgmod这些。文件所有者、同组、其他用户权限 UID/GID 实际用户ID和实际组ID即登陆时候的用户名比如我是lirobins登陆那么UID/GID 为lirobins/lirobins EUID/EGID 有效的用户ID和有效的组ID它们指定了访问目标的权限。 当前登录用户为myapps文件的ls -l 后的用户为my组为mygroup此时UIDmy,GIDmygroup,EUIDmy,EGIDmygroup。 当前登录用户为myapps文件的ls -l 后的用户为other,组为othergroup此时UIDmy,GIDmygroup,EUIDother,EGIDothergroup。并且my用户无法访问other用户的文件。 SUID/SGID 针对文件而讲述的概念他可以修改当前进程的EUID/EGID chmod us filename # 设置SUID位 chmod u-s filename # 去掉SUID设置 chmod gs filename # 设置SGID位 chmod g-s filename # 去掉SGID设置 .SUID权限 当一个具有执行权限的文件设置SUID权限后用户执行这个文件时将以文件所有者的身份执行。 特点只有可以执行的二进制程序才能设定SUID权限(可执行 命令执行者要对该程序拥有x执行权 命令执行者在执行该程序时获得该程序文件属主的身份 SUID权限只在该程序执行过程中有效就是说身份改变只在执行过程中有效。 可执行文件/usr/bin/passwd所属用户是rootUID为0此文件被设置了SUID权限。当一个UID为1000、GID为1000的用户执行此命令时产生的进程RUID和RGID分别是1000和 1000EUID是0、EGID是1000。 user1用户登陆取得了一个bash的shell类型。然后 执行passwd命令。在这个命令执行过程中执行命令的身份被切换成了root用户。 在用户权限上 有一个s这就是说明该文件设置了SUID权限。也就是说在普通用户在执 行passwd命令的时候是使用root的身份。 还有capabilities 更细粒度的权限用位图表示权限 内存管理 每个进程都有自己独立的虚拟内存空间这里需要一个数据结构来表示mm_struct 文件与文件系统 每个进程都有一个文件系统和打开文件的数据结构 用户态函数栈 用户态中程序的执行往往是一个函数调用另一个函数函数调用通过栈来执行的函数调用就是指令跳转重点是参数和返回地址怎么传递过去。具体的看另一篇。 内核态函数栈用于进程实现系统调用 成员变量 stack 在内核中各种各样的函数调用就用到了。Linux 给每个task 都分配了内核栈。 内核栈分布:空间最低的位置是一个 thread_info 结构这个结构是对 task_struct 结构的补充。 因为 task_struct 结构庞大但是通用不同的体系结构就需要保存不同的东西与体系结构有关的都放在 therad_info 里。 内核代码里有这样一个 union 将 thread_info 和 stack 放在一起 在内核栈的最高地址端存放的是另一个结构 pt_regs.当系统调用 从用户态到内核态的时候要做的第一件事情就是将用户态运行过程中的CPU 上下文保存起来主要就是保存在这个结构的寄存器变量里 pt_regs。 调度如何制定项目管理流程 在Linux里面进程大概可以分为两种 一种叫做实时进程也就是需要尽快执行返回结果的那种优先级一般比较高 一种叫做普通进程大部分进程其实都是这种按照正常流程完成即可 很显然对于这两种进程我们的调度策略肯定是不同的。 在task_struct中有一个成员变量叫做调度策略对于实时进程优先级的范围是0~99 对于普通进程优先级是100~139 SCHED_FIFO、SCHED_RR、SCHED_DEADLINE都是实时进程的调度策略 SCHED_FIFO 高优先级的进程可以抢占低优先级的进程而相同优先级的进程先来先服务 SCHED_RR 轮流调度算法高优先级的进程可以抢占低优先级的进程而相同优先级的任务采用时间片算法当相同优先级的任务用完时间片会被放到队列外部以保证公平性 SCHED_DEADLINE 按照任务的deadline进行调度。当产生一个调度点的时候DL调度器总是选择其deadline具体当前时间点最近的那个任务执行 对于普通进程的调度策略有SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE。 SCHED_NORMAL普通进程 SCHED_BATCH后台进程这些进程可以默默执行不要影响需要交互的进程可以降低它的优先级 SCHED_IDLE特别空闲的时候才跑的进程 调度策略的执行逻辑就封装在这里面它是真正干活的那个。 sched_class有几种实现 stop_sched_class 优先级最高的任务会使用这种策略会中断所有其他线程而且不会被其他任务打断 dl_sched_class 对应上面的 deadline 调度策略 rt_sched_class就对应 RR 算法或者 FIFO 算法的调度策略具体调度策略由进程的task_struct-policy 指定 fair_sched_class普通进程的调度策略 idle_sched_class 就是空闲进程的调度策略。我们平常都是普通进程也就是说fair_sched_class用的最多。fair_sched_class顾名思义对普通进程来讲公平是最重要的 调度器的数据结构 完全公平调度算法 在linux里面实现了一个基于CFSCompletely Fair Scheduling完全公平调度的调度算法。其实现原理是 安排一个虚拟进行时间如果一个进程在执行随着时间的增长也就是一个个tick的到来进程的vruntime将不断增大。没有得到执行的进程vruntime不变显然那些 vruntime 少的原来受到了不公平的对待需要给它补上所以会优先运行这样的进程。 从上面可以推导出CFS需要一个数据结构来对vruntime进行排序找出最小的那个。 不但查询要快而且因为经常变所以更新也要快 能够平衡查询和更新速度的树一般用的是红黑树。红黑树的节点是应该包括vruntime的称为调度实体。如果这个进程是个普通进程则通过sched_entity将自己挂载在这颗红黑树上。 那这颗红黑树放在哪里呢一个任务队列中 每个CPU都有自己的struct rq结构用于描述在此CPU上所运行的所有进程其中包括一个实时进程队列rt_rq和一个CFS运行队列cfs_rq 在调度时调度器首先会先去实时进程队列找是否有实时进程需要运行如果没有才会去CFS运行队列中找是否有进程需要运行 串起来看每个CPU都有自己的队列rq这个队列里面包含多个子队列比如rt_rq和cfs_rq不同的队列有不同的实现方式cfs_rq就是用红黑树实现的。当某个CPU需要找下一个任务执行的时候会按照优先级依次调用类。先在实时队列找任务没有的话再到fair_sched_class被调用它会在cfs_rq上找下一个任务。 调度类涉及的函数就是 向就绪队列中添加一个进程当某个进程进入可运行状态时调用这个函数删除一个进程pick_next_task 选择接下来要运行的进程等等。 前面说的主要是如何选择下一个任务。下面要说什么时候开始选择。 调度主要有两种方式 方式一A任务做着做着发现里面有一条指令sleep就是要休息一下或者在等待某个IO时间。那就没有办法就要主动让出CPU然后就可以开始做B任务了主动调度 方式二A任务做着做着旷日持久实在受不了了。项目经理介入了说这个任务A先听听B任务也要做一下要不然B任务就该投诉了抢占式调度 主动调度一般发生在网络IO等待用不到CPU时选择主动调度schedule()。调度完成的事情 1、在当前的CPU上我们取出任务队列rq。task_struct *prev指向这个CPU的任务队列上面正在运行的那个进程curr。为啥是prev因为一旦将来它被切换下来那它就成为了前任了。 2、 next pick_next_task(rq, prev, rf); clear_tsk_need_resched(prev); clear_preempt_need_resched();获取下一个任务task_struct *next指向下一个任务。pick_next_task已经说过了如果不是实时的就是在红黑树取最左边的节点。更新前任的虚拟时间放回到红黑树中。set_next_entity将继任者设为当前任务 3、第三步就是进行上下文切换继任者进程正式进入运行。 上下文切换主要干两件事情一是切换进程空间也就是虚拟内存页表块表等二是切换寄存器和CPU上下文这里其实就是项目中协程切换的原理了。 抢占式调度 最常见的现象就是一个进程执行时间太长了就会切换到另一个进程。主要用时钟中断处理函数会调用scheduler_tick() void scheduler_tick(void) {int cpu smp_processor_id();struct rq *rq cpu_rq(cpu);struct task_struct *curr rq-curr; ......curr-sched_class-task_tick(rq, curr, 0);cpu_load_update_active(rq);calc_global_load_tick(rq); ...... } 这个函数先获取当前CPU的运行队列然后去这个队列上获取正在运行中的进程的task_struct然后调用这个task_struct的调度类task_tick函数顾名思义这个函数就是用来处理时钟事件的。 如果当前运行的进程是普通进程调度类为fair_sched_class调用的处理时钟的函数为task_tick_fair实现如下根据当前进程的task_struct找到对应的调度实体sched_entity和cfs_rq队列调用entity_tickupdate_curr更新当前进程的vruntime然后调用check_preempt_tick顾名思义就是检测是否是时候被抢占了。 怎么判断是否可以被抢占 sum_exec_runtime-prev_sum_exec_runtime 就是这次调度占用实际时间。如果这个时间大于 ideal_runtime则应该被抢占了。 除了这个条件之外还会通过 __pick_first_entity 取出红黑树中最小的进程。如果当前进程的 vruntime 大于红黑树中最小的进程的 vruntime且差值大于 ideal_runtime也应该被抢占了。 当发现当前进程应该被抢占不能直接把它踢下来而是把它标记为应该被抢占。为什么呢因为所有进程切换都必须等待正在运行的进程调用__schedule才行所以这里只能先标记一下。 另一个可能抢占的场景是当一个进程被唤醒的时候当被唤醒的进程优先级高于CPU上的当前进程就会触发抢占。到这里你会发现抢占问题只做完了一半。就是标识当前运行中的进程应该被抢占了但是真正的抢占动作并没有发生。 真正的抢占还需要时机也就是需要那么一个时刻让正在运行中的进程有机会调用一次__schedule。 代码里面不可能像主动调度一样写一个schedule函数所以一定要规划几个时机这个时机分为用户态和内核态 对于用户态的进程来讲从系统调用中返回的那个时刻是一个被抢占的时机。可以看到在exit_to_usermode_loop函数中上面打的标记起了作用如果被打了_TIF_NEED_RESCHED调度shcedule进行调度选择一个进程让出CPU做上下文切换。 对于用户态的进程来讲从中断中返回的那个时刻也是一个被抢占的时机 二者不一样吗系统调用不就是要陷入中断int 80 对内核态的执行中被抢占的时机一般发生在preempty_enbale()中 在内核态的执行中有的操作是不能被中断的所以在进行这些操作之前总是先调preempt_disable()关闭抢占当再次打开的时候就是一次内核态代码被抢占的机会。 内核态也会遇到中断的情况当中断返回的时候如果返回的仍然是内核态这个时候也是一个被抢占的时机。 总结一下 调度的基本策略、基本结构、调度两种方式主动和抢占。调度主要是选择下一个进程进程切换虚拟内存和CPU上下文。抢占式调度先标记一下再合适的时机真正调度比如系统调用返回、中断返回时 下面说一下进程和线程的创建 fork在内核里面做了什么事情 fork解封装成sys_fork,宏里面定义调用do_fork函数。 做的第一件大事就是copy_process复制的是task_struct这个进程数据结构。 主要做的事情是:alloc一块task_struct空间alloc_thread_stack_node 创建内核栈分配一个连续的THREAD_SIZE的内存空间赋值给task_struct的void *stack成员变量调用memcpy复制task_struct; 调用setup_thread_stack 设置thread_info。 这一步就是alloc分配空间memcpy复制 接下来就是权限相关了copy_creds主要做了下面几件事情 retval copy_creds(p, clone_flags);也是分配cred结构并memcpy复制 接下来是统计信息重置比如用户内核态运行时间值为0开始时间睡眠和不包含睡眠设置 retval sched_fork(clone_flags, p);设置调度相关的变量。 接下来copy_process开始初始化与文件和文件系统相关的变量 retval copy_files(clone_flags, p);主要用于复制一个进程打开的文件信息fd文件描述符 retval copy_fs(clone_flags, p);主要用于复制一个进程的目录信息。根目录当前目录的文件系统 接下来copy_process开始初始化与信号相关的变量 接下来copy_process开始复制进程内存空间 retval copy_mm(clone_flags, p);copy_mm函数中调用dup_mm分配一个新的mm_struct调用memcpy复制这个函数 dup_mmap用于复制内存空间中内存映射的部分mmap除了可以分配大块的内存还可以将一个文件映射到内存中方便可以像读写内存一样读写文件 接下来copy_process开始分配pid、设置tid、group_leader并且建立进程之间的亲缘关系。 总结一下首先就是分配task_struct空间和内核栈空间并用memcpy复制接着还有文件权限、统计信息、文件系统、内存空间文件映射、pid亲缘设置等。 fork的第二件大事唤醒新进程 _do_fork 做的第二件大事是 wake_up_new_task。新任务刚刚建立有没有机会抢占别人获得CPU呢 首先将进程的状态设置为TASK_RUNNING activate_task函数会调用enqueue_task加入到队列中设置更新一些统计量队列数量虚拟运行时间等 3、然后wake_up_new_task 会调用check_preempt_curr看是否能够抢占当前进程。fork是一个系统调用从系统调用返回的时候就是一个抢占的好时机如果父进程判断自己已经被设置为TIF_NEED_RESCHED就让子进程先跑抢占自己 总结我们知道了进程的创建过程复制task_struct以及唤醒新进程看看是不是要抢占前面调度策略已经说了一般抢占调度一个场景就是一个进程被唤醒的时候如果优先级高就会抢占。 linux线程的创建 **其实在内核中线程和进程没有区别的调度进程和线程都是一样的。都是统一的task_struct结构体放在链表中调度策略也一样。连pid都一样。唯一不同的就是线程用的是进程的内存空间以及文件系统不用复制了直接copy_vm标记。**五大结构仅仅是引用计数加一文件描述符文件系统内存空间信号也就是线程共享进程的数据结构。 线程不是一个完全由内核实现的机制它是由内核态和用户态合作完成的 pthread_create不是一个系统调用是glibc库的一个函数。最重要的就是有一个入口函数代表线程要完成的事情。因此 在用户态也有一个用于维护线程的结构就是这个pthread结构线程id就是它维护的每个线程都有自己的栈。因此需要创建线程栈。因为是一个函数只要有函数调用就会有栈帧 线程栈的分配是在进程的堆空间里分配的只不过它是固定的一块大小而已。线程堆的分配在windows中是共享的因此需要同步机制的保护。而linux中线程的堆是独占的不需要同步保护销毁时自动回收了。 接下来就是内核态的栈。调用的是系统调用clone在copy_process函数里面task_struct复制一遍。其中Pid变了tgid没变代表新建了线程。
http://www.w-s-a.com/news/300913/

相关文章:

  • 网站带后台品牌网页设计图片
  • 保定清苑住房和城乡建设局网站分类信息网站程序
  • 可以做视频推广的网站选择大连网站建设
  • 在线网站开发网站在哪里
  • 建站的步骤上海快速优化排名
  • 招聘网站做一下要多少钱网站设计公司 国际
  • 巩义专业网站建设公司首选seo研究院
  • 大流量网站解决访问量友情链接如何添加
  • 教育网站建设网永康市住房和城乡建设局网站
  • 阿里巴巴官网网站django 做网站的代码
  • 网站建设 军报wordpress 订餐模板
  • 网站虚拟主机 会计处理石家庄站建设费用多少
  • 网站建设 服务内容 费用简述网站开发流程
  • 公司制作网站跟企业文化的关系空间制作网站
  • 浙江建设监理协会网站个人网站设计规划书
  • wordpress太卡了贵州seo推广
  • 企业介绍微网站怎么做的手机软件商城免费下载
  • 新手网站设计定价网站开发销售
  • 网站开发公司oa有没有找人做标书的网站
  • 传统门户网站有哪些人武部正规化建设
  • 台州网站制作方案免费无代码开发平台
  • 精通网站建设 pdf微盘学做电商的步骤
  • 想在网上做设计接单有没有网站找一个免费域名的网站
  • 湘潭市网站建设科技有限公司杭州网站建设(推荐乐云践新)
  • 优秀网站评析西双版纳傣族自治州民宿
  • 常用的cms建站系统c2c网站模板
  • wordpress更换图标seo网站建设公司
  • 网站备案 深圳小程序怎么进入公众号
  • 实名认证域名可以做电影网站吗坪山网站设计的公司
  • wdcp怎么上传做好的网站管理咨询公司名称参考