linux内核启动(2)——2.2.创建和中止任务与内核线程

2.2.        创建和中止任务与内核线程
不同的操作系统书籍,从一个“正在执行的程序的实例”到“由clone或者fork系统调用产生的任务”等不同方式定义了“进程”。在linux下,共有三种类型程序:
        空线程;
        内核线程;
        用户任务;
空线程在为第一个CPU引导时创建,然后依靠定义在arch/i386/kernel/smpboot.c的fork_by_hand()函数手工为每个CPU创建这个线程。所有的空线程共享一个init_task结构,但都拥有各自的存放在CPU队列里的init_tss表示的TSS结构。他们以CLONE_PID方式clone,PID都为零,其他任务都不能共享这个PID。
内核模式下,kernel_thread函数调用clone系统调用创建了内核线程。内核线程通常没有用户地址空间,也就是p->;mm=NULL,因为他们明确通过daemonize()函数执行exit_mm()函数。内核线程通常可以直接操作内核地址空间,并被分配低范围的pid号。当在处理器模式下运行时意味着内核线程将享用所有的I/O特权并不能被调用程序预清空。
用户任务通过clone或者fork系统调用创建,他们都在内部调用了kernel/fork.c的do_fork()函数。
让我们分析一下当用户进程调用fork系统调用时发生了什么。虽然fork操作在传递用户堆栈和寄存器时依赖于体系架构,但在下面真实执行这个操作的do_fork()函数确实简洁的,并位于kernel/fork.c文件。
以下步骤将被执行:
1)        本地变量被设置为-ENOMEM,当fork创建一个新任务结构失败时将作为错误代码返回。
2)        .如果CLONE_PID标识被设置,则返回-EPERM错误,除非调用者是空线程。普通用户线程clone时不能传递CLONE_PID标识并期待操作成功。SIFCHLDclone标识,对于fork来说,它被认为是不相关的,仅在sys_clone调用do_fork时才被认为是相关的。
3)        初始化current->;vfork_sem。它将在sys_vfork函数为了休眠父进程直到子进程执行mm_release函数时使用,就像执行其他程序或者中止其他程序一样。
4)        通过alloc_task_struct()宏分配一个新的任务结构。在x86系统上,它仅是一个GFP_KERNEL优先级的gfp。这酒是为何fork系统调用可能休眠的第一个原因。如果分配失败,返回-ENOMEM错误。
5)        通过结构拷贝*p = *current,将所有当前进程结构的数据都拷贝到新进程,或许这个操作应该被memcpy替换。然后,所有不能被子进程修改的字段将被设置为正确的值。
6)        大范围的内核锁被采用以防止其他部分执行本段代码。
7)        如果父进程拥有用户资源则校验是否超出了RLIMIT_NPROC限制。如果是这样,则返回-EAGAIN错误;如果没有,则通过指定的uid将计数器p->;user->;count进程数刷新。
        如果系统所有的任务数目超过了最大线程数,返回-EAGAIN错误。
9)        如果进程是依赖预模块执行的,则增加依赖模块的引用计数。
10)        如果进程是依赖预模块二进制格式的,也增加依赖模块的引用计数。
11)        子进程被标识为“没有被执行”(p->;did_exec=0)。
12)        子进程被标识为'not.swappable' (p->;swappable = 0)。
13)        子进程被置为TASK_UNINTERRUPTIBLE状态,即p->;state = TASK_UNINTERRUPTIBLE。
14)        依照clone_flags的数值设置子进程的p->;flags,如果是简单fork,p->;flags= PF_FORKNOEXEC。
15)        通过快速算法kernel/fork.c的get_pid()函数设置子进程号p->;pid。
16)        初始化子进程其他任务结构。最后子进程结构被插入到pidhash表中,并且被唤醒。
这样任务就被创建了。停止任务有很多方式。
1)        通过exit系统调用;
2)        收到一个中止信号;
3)        被确定异常强制中止;
4)        以func == 1参数调用bdflush。
系统调用的实现函数都有sys_前缀,当他们通常仅与参数检测或者以细节方式传递信息,真正的操作是由do_**函数完成的。所以sys_exit()函数调用了do_exit来完成操作。尽管如此,内核其他部分有时也通过调用sys_exit实现堆do_exit的调用。
do_exit函数定义在kernel/exit.c中,按照以下几个步骤执行:
        获取内核全局锁;
        在最后调用一直循环的schedule()函数;
        设置任务状态为TASK_ZOMBIE;
        以current->;pdeath_signa信号通知所有的子进程;
        以等同于SIGCHLD的信号current.>;exit_signal通知父进程;
        释放fork函数分配的资源,关闭已经打开的文件;
        在采用少量FPU切换的体系中,不管硬件设备要求什么都向FPU的所有者传递一个“none”;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值