一、 进程创立:

一、进程的创办步骤以及开创函数的牵线

1 进程

过程指的是居于执行期的先后。不过须要专注的是经过并不仅仅囊括一段可执行程序的代码,它同时还包涵其他资源,例如打开的文书,挂起的信号,内核内部数据,处理器状态,具有内存映射的地方空间和实践线程以及数据段等。

进程是具有操作系统的主导概念,同样在linux上也不例外。

  Unix
下的进度创立很更加,与广大此外操作系统差别,它分两步操作来创制和执行进程:
fork() 和 exec() 。首先,fork()
通过拷贝当前进程创立一个子进度;然后,exec()
函数负责读取可执行文件并将其载入地址空间初步运行。

1、使用fork()或者vfork()函数创设新的进程

1.1 进度描述符

一个操作系统要是想治本好进度,那么操作系统就需求以此进程的享有新闻,Linux内核成功抽象了经过这一概念,然后接纳task_struct即经过描述符来对进度展开管理,同时内核使用双向链表(即任务队列)对进程描述符进行了相应的团队。(task_struct结构体定义在<linux/sched.h>)。

澳门金沙国际 1

task_struct和职务队列

task_struct在32位处理器中占有1.7KB。包涵一个过程具有的新闻,包括打开的文书,进度地址空间,挂起的信号,进度意况等,具体可以参考在Linux内核代码中定义的task_struct结构体代码。Linux在分配进度描述符时,使用了slab机制(可以查看进度成立一节)。当进度描述符task_struct分配殆尽之后,需求对其进行存放。

最首要内容:

1、fork() :kernel/fork.c

2、条用exec函数族修改创造的经过。使用fork()创制出来的经过是时下进程的通通复制,可是我们创造进程是为着让新的长河去实践新的次第,因而,就必要用到exec函数族对成立出来的新历程展开改动,让他具有和父进度分歧的事物,修改后就可以实施新的主次,当然,修改后的子进度包涵了要进行顺序的音信。

1.2 内核进度操作

对于一个经过来说,在内存中会分配一段内存空间,一般的话这一个空间为1要么2个页,这几个内存空间就是经过的内核栈。在经过内核栈的栈底有一个结构体变量为thread_info,这么些结构体变量中蕴藏了一个针对该进度描述符task_struct的指针,那一个变量的存在,可以使基本快速地赢得某一个进程的进度描述符,从而增强响应速度。在x86连串布局中,内核中的current宏就是经过对于这么些结构体的造访来落实的,而在任何寄存器丰硕的系统布局中看,可能会并未行使thread_info结构体,而是径直动用某一个寄存器来形成例如PPC种类布局。

/*x86中thread_info的定义*/
struct thread_info {
    struct task_struct  *task;      /* main task structure */
    struct exec_domain  *exec_domain;   /* execution domain */
    unsigned long       flags;      /* low level flags */
    unsigned long       status;     /* thread-synchronous flags */
    __u32           cpu;        /* current CPU */
    int         preempt_count;  /* 0 => preemptable, <0 => BUG */

    mm_segment_t        addr_limit; /* thread address space:
                         * 0-0xBFFFFFFF for user-thead
                         * 0-0xFFFFFFFF for kernel-thread
                         */
    struct restart_block    restart_block;
    __u8            supervisor_stack[0];
};
  • 进度和线程
  • 经过的生命周期
  • 进程的创设
  • 进程的告一段落

  在Linux系统中,通过调用fork()来创制一个经过。调用 fork()
的进程称为父进度,新发生的进度称为子进程。在该调用停止时,在再次回到点那个相同的坐席上,父进度复苏执行,子进度始起履行。fork()系统调用从根本再次回到五次:一遍回到到父进度,另一遍回到到新发生的子进度。使用fork()创设新历程的流水线如下:

在Linux中,fork()和vfork()就是用来创建进程的多少个函数,他们的相干音讯如下:

1.3 进程PID

Linux的基本使用PID来对经过展开唯一标识。PID是pid_t的盈盈类型,PID的值备受<linux/threads.h>头文件中规定的最大值的限量,可是为了和传统的Unix操作系统包容,PID会被默许设置为32768即short
int短整型的最大值。PID的最大值是系统中允许同时存在的长河的最大数目。PID
的最大值可以经过/proc/sys/kernel/pid_max来修改。

1. 进度和线程

进程和线程是程序运行时情形,是动态变化的,进度和线程的田间管理操作(比如,创设,销毁等)都是有基本来促成的。

Linux中的进度于Windows比较是很轻量级的,而且不严俊区分进度和线程,线程不过是一种特其他长河。

由此上边只谈谈进程,唯有当线程与经过存在不均等的地点时才提一下线程。

 

经过提供2种虚拟机制:虚拟处理器和虚拟内存

各样进程有单独的杜撰处理器和虚拟内存,

每个线程有独立的杜撰处理器,同一个进度内的线程有可能会共享虚拟内存。

 

基础中经过的音讯根本保存在task_struct中(include/linux/sched.h)

经过标识PID和线程标识TID对于同一个经过或线程来说都是相等的。

Linux中可以用ps命令查看所有进度的新闻:

ps -eo pid,tid,ppid,comm

 

  1)fork() 调用clone;

始建进度函数:

1.4 进度家族树

Linux和Unix系统一样,进程之间存在鲜明的存续关系。所有的进程都是PID为1的init进度的子孙。内核会在系统启动的尾声阶段启动init进度,那些历程回去读取并且实施系统的开首化脚本(initscript)执行有关程序,已毕全套系统的启动。
在Linux操作系统中,每个进程都会有父进程,每个进程都会有0到n个子进度。同一个父进度的具备进度被称作兄弟。进度描述符中,包罗了指向父进程的指针,还富含了一个children子进度链表(init进度的长河描述符是静态分配的)。所以经过简单的遍历就可访问到系统中的所有进度。在代码中越发提供了for_each_process(task)宏来举行对任何进程队列(或尽职分队列)的拜访能力。

2. 进程的生命周期

进度的相继状态之间的转折构成了经过的漫天生命周期。

澳门金沙国际 2

 

  2)clone() 调用 do_fork();

pid_t fork(void)//成功重回0,战败重回-1

2 进度创设

3. 经过的创建

Linux中成立进度与其他系统有个关键差异,Linux中开创进程分2步:fork()和exec()。

fork: 通过拷贝当前历程成立一个子历程

exec: 读取可执行文件,将其载入到内存中运行

创办的流程:

  1. 调用dup_task_struct()为新历程分配内核栈,task_struct等,其中的始末与父进度相同。
  2. check新进程(进度数目是还是不是超出上限等)
  3. 理清新进度的音信(比如PID置0等),使之与父进度差异开。
  4. 新历程情形置为 TASK_UNINTERRUPTIBLE
  5. 更新task_struct的flags成员。
  6. 调用alloc_pid()为新进程分配一个卓有功用的PID
  7. 依据clone()的参数标志,拷贝或共享相应的音讯
  8. 做一些了结工作并赶回新历程指针

始建进度的fork()函数实际上最终是调用clone()函数。

创办线程和进程的手续一样,只是最后传给clone()函数的参数分裂。

譬如,通过一个常备的fork来成立进程,相当于:clone(SIGCHLD, 0)

创造一个和父进度共享地址空间,文件系统资源,文件讲述符和信号处理程序的历程,即一个线程:clone(CLONE_VM
| CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)

进度创建函数fork,Linux内核学习之进度和线程初探。在基本中创制的基石线程与平日的历程之间还有个基本点不一样在于:内核线程没有独立的地址空间,它们只能够在基础空间运行。

这与事先涉嫌的Linux内核是个单内核有关。

 

  3)do_fork() 调用 copy_process() 函数,copy_process() 函数将不负众望第
4-11 步;

fork()用于创建新的进度,所创制进度为当下历程的子进程,可以通过fork()函数的归来质6来决定进程是在父进度中仍然在子进度中。即使运行在父进度中,则赶回PID为子进度的进度号,假若在子进度中,则赶回的PID为0

2.1 成立进度

在Linux进度成立分裂于其他操作系统,Linux操作系统提供了三个单身的函数姣好进程的创办工作。其中fork()函数由此拷贝完结子进程的始建,子进程会完全拷贝父进度中的绝大部分资源,(除了PID和PPID,以及部分机智资源和总计量)。然后在接纳exec()函数成功可执行文件的读取,并且将其载入地址空间运行。而任何操作系统一般只使用一个函数完结上述的两步操作。

fork()函数是通过clone()系统调用兑现的。此调用会通过一层层参数标志指明父子进度要求共享的资源。库函数基于参数标志调用clone()clone()调用do_fork()函数do_fork()函数在kernel/fork.c中定义,并且成功了创办中的半数以上工作。然后该函数会去调用copy_process()函数copy_process()函数完结了下述工作:
1)
调用duo_task_struct()函数为新进度创立内核栈thread_info、和task_struct,不过那个值都和当前进度的一模一样,只是一份大概的复制
2) 检查当前用户的经过总数是还是不是超越限制
3)
将进度描述符中有关当前进程的计算音讯清零,使得子进度和父进度可以举行区分
4) 将子进程情形设为TASK_UNINTERRUPTIBLE,使其不可能运行
5)
调用copy_flag()函数更新task_structflags成员。将最佳用户权限标志符PF_SUPERPRIV清零,然后将经过未调用exec()函数标志位PF_FORKNOEXEC置位。
6) 调用alloc_pid()为新进程分配一个得力PID
7)
按照传递给clone()的参数标志,该函数(即copy_process()函数)拷贝或者共享打开的文本、文件系统音信、信号处理函数、进度地址空间和命名空间。
8) 扫尾,然后回到一个指向子进度的指针

当然还有其余格局的fork()函数完成格局。例如vfork()函数功能和fork()函数相同,但是vfork()函数不会拷贝父进度的页表项。vfork()变动的子进程作为一个单独的线程在其地址空间内运行,父进度会被堵塞,直到子进程退出或者调用exec()函数,子进度不允许向地点空间内写入数据。可是在使用了写时拷贝技术日后,这一项技艺其实早就无关首要了。

4. 经过的平息

和开创进程一样,终结一个进度同样有广大手续:

 

子进程上的操作(do_exit)

  1. 设置task_struct中的标识成员设置为PF_EXITING
  2. 调用del_timer_sync()删除内核定时器, 确保没有定时器在排队和周转
  3. 调用exit_mm()释放进度占用的mm_struct
  4. 调用sem__exit(),使进程离开等待IPC信号的行列
  5. 调用exit_files()和exit_fs(),释放进度占用的文本讲述符和文件系统资源
  6. 把task_struct的exit_code设置为经过的重回值
  7. 调用exit_notify()向父进度发送信号,并把自己的气象设为EXIT_ZOMBIE
  8. 切换来新进程继续执行

子进度进入EXIT_ZOMBIE之后,即使永远不会被调度,关联的资源也释放掉了,然则它本身占用的内存还从未自由,
譬如说创立时分配的内核栈,task_struct结构等。那一个由父进程来刑释解教。

父进度上的操作(release_task)

父进度受到子进度发送的exit_notify()信号后,将该子进度的经过描述符和所有进度独享的资源总体删减。

从上边的步子可以寓目,必须求力保每个子进程都有父进程,若是父进度在子进度截至之前就已经完成了会什么呢?

子进度在调用exit_notify()时曾经考虑到了那一点。

假诺子进程的父进度已经退出了,那么子进度在退出时,exit_notify()函数会先调用forget_original_parent(),然后再调用find_new_reaper()来搜寻新的父进度。

find_new_reaper()函数先在时下线程组中找一个线程作为二叔,若是找不到,就让init做父进度。(init进度是在linux启动时就径直存在的)

  4)调用 dup_task_struct()
为新进度创设一个内核栈、thread_info结构和task_struct,这么些值与眼前进程的值相同;

pid_t vfork(void)//成功再次回到0,失利再次来到-1

2.2 进度成立优化

出于经过描述符task_struct是一个在经过创建时必须的数据结构,所以经过的创制速度可以由此加速进度描述符的创办来增进,有鉴于此,内核使用了slab机制来对其开展拍卖。所谓slab机制,就是对于频仍被运用的数据结构,会进行缓存,而不是运用已毕之后直接举办释放。那样做的功利是,如果急需反复成立某一数据结构变量,只是平素采取即可,而不要求举办内存的申请,使用已毕也不须要释放,大大减少了分配内存和回收内存的年华。使用slab机制后,进度描述符可以被连忙地建立,同时经过销毁时也不必要去开展进度描述符的内存释放。

自然Linux内核在其余方面也使用了加快进度制造的点子。上面讲到,Linux创立进度使用fork()函数来完成,而fork()函数又使用clone()系统调用来兑现,不过急需注意的是,创建一个新进程时,Linux内核参预了写时拷贝机制来增速进度的创始,而不是完全地对进程具有内容举办简要的复制。所谓写时拷贝即便在新进程创设时,子进度和父进度共享一个进度地址空间拷贝,当子进程或者父进度对那一个拷贝执行写入操作后,数据才会被复制,然后开展独家的改动,所以资源在未举行写入时,以只读格局共享。这种写时拷贝的措施,将经过的开创费用从子进度对父进度资源的大气复制,简化为复制父进度的页表和子进度唯一进度描述符的创设

 
5)检查并保管新成立这一个子进度后,当前用户所享有的历程数目没有超出给它分配的资源的界定;

vfork()函数和fork()函数相比相近,都用来创设子进度。只是其用来创建新的进程,父子进度共享虚拟内存空间。但是在根本中,vfork()的落实任然调用fork()函数,调用函数如下:

2.3 进程终结

经过终结时,内核必必要自由他所占据的资源,然后布告父进度。进度的析构发生在exit()系统调用时,可以是显式的,也得以是隐式的,例如从某个程序的主函数重临(对于C语言来说实在会在main()函数的再次回到点前边设置exit()代码)。当进度收到不可能处理不过又不可能忽视的信号或者出现卓殊时,也说不定会被动终结。不过经过在为止是,大多数如故会调用do_exit()完成(在kernel/exit.c中定义)。
(1) 将task_struct中的标志成员设置为PF_EXITING
(2)
调用del_timer_sync()剔除任意内核定时器。依据再次来到的结果认可没有别的定时器在排队,同时也尚未别的定时器处理程序在运行。
(3)
若开启了BSD的经过记账功用,那么还索要调用acct_update_integrals()来输出记账新闻
(4)
调用exit_mm()刑满释放进程占用的mm_struct,假若没有别的进程使用那一个地址空间,那么就到底释放此位置空间
(5) 调用sem_exit()函数,若进程排队等候IPC信号,则离开队列
(6)
调用exit_file()exit_fs(),分别递减文件描述符、文件系统数据的引用计数。若释放后引用计数为0,则直接出狱。
(7)
将存放在task_struct的exit_code成员中的职分退出代码置为由exit()提供的天职退出代码,或者完成其他其余由基本机制规定的淡出动作。退出代码的存放是为了供父进度检索
(8)
调用exit_notify()函数向和睦的父进度发送信号,并且给自己的子进度重新寻找养父,养父为线程组中的别的线程或者为init进程,然后将经过境况置为EXIT_ZOMBLE
(9)
do_exit()调用schedule()切换到新历程。那是do_exit()举行的尾声代码,退出后就不再重临。

 
6)清理子进度经过描述符中的一些分子(清零或开头化,如PID),以使得子进度与父进程分歧开来;

sys_vfork(struct pt_regs *regs)
{
        return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD,
regs->gr[30], regs, 0, NULL, NULL);
}

2.3.1 删除进度描述符

经过在进行完do_exit()函数调用之后,会处于EXIT_ZOMBIE脱离状态,其所占有的内存就是内核栈澳门金沙国际 ,thread_info结构task_struct结构体。处于这些情状的进度唯一目标就是向父进度提供音讯。父进度检索到新闻照旧文告内核那是前言不搭后语的新闻后,由进程所负有的盈余的内存释放。

调用do_exit()后来,就算线程已经僵死不再运行,可是系统还保存了它的进程描述符。那样做可以使系统能够在子进度终结后仍得到其消息。所以经过的完工清理操作可以和进程描述符的删减操作分开运行。
在剔除进度描述符的时候,会调用release_task(),达成以下操作:
(1)调用__exit_signal(),由次函数调用_unhash_process(),后者又调用detach_pid()pidhash上剔除该进度,同时从任列表中删去该进度
2)__exit_signal()放出近年来僵死进度所利用的有着盈余资源,并开展最终的计算和笔录。
3)若是那几个进度是进度组最终一个进度,并且领头进度早已死掉,那么release_task()通知僵死的领衔进度的父进度
4)调用put_task_struct()释放经过内核栈thread_info结构所占据的页,释放task_struct所占的slab高速缓存

若父进度在子进程此前退出,则第一会为子进度在当下历程组内宣召一个进程作为大爷,若卓殊,就让init进程作为父进度。

  7)将子进度的状态设置为 TASK_UNINTERRUPTIBLE,有限支撑它不会投入运作;

 

3 线程

线程是指在经过中活动的靶子,相对而言,线程仅仅局限在进程之中,线程拥有的资源远远比进度小,仅仅包涵独立的次第计数器和经过栈以及一组经过寄存器。在其余操作系统中经过和线程的概念往往会被严峻区分,而是对于Linux操作系统内核而言,它对线程和进度并不进行区分,线程日常被视为一个与其余进程共享某些资源的进度。每个线程都持有自己的task_struct,所以线程在Linux内核中也被视为一个进度,那是和其余操作系统截然不一致的。
线程的创办和进度是类似的而是在调用clone()的时候,会传递一些特种的标志位,例如CLONE_VMCLONE_FSCLONE_FILESCLONE_SIGHAND,那些值都是由下表定义的。

澳门金沙国际 3

澳门金沙国际 4

clone()参数标志

根本很多时候还索要在后台执行一些操作,这一个都是由基础线程(kernel
thread)
做到。内核线程独立于内核进度运行,同时内核线程没有单身的位置空间,并且不会切换来用户空间,其余和平时线程一样,没有区分。
水源线程一般是自动从根本进度中衍生而出,同样内核线程也是经过clone()系统调用落到实处,并且要求调用wake_up_process()函数来举办精晓地擢升。kthread_run()可以落成线程的提示和运行,但是精神上只是调用了kthread_create()wake_up_process()。内核线程可以采取do_exit()函数退出,也足以由基础其余一些调用kthread_stop()函数来拓展剥离。

  8)调用 copy_flags() 以更新 task_struct 的 flags 成员;

 

4 进度和线程的区分

对于Linux内核而言,进度和线程没有分别。对于Linux内核而言,并没有对线程举办分外处理,而是将线程与经过不分厚薄,那与其余操作系统完全分裂。其余操作系统都提供了专门的建制去完毕八线程机制,由于Linux强大轻便迅速的长河创设手段,所以Linux仅仅将线程看作是经过共享了经过资源的多个进度,对于Linux内核来说创立线程等价于创立一个进程。通过Linux内核可以识破,一个经过的十二线程其实只是共享了诸多资源,例如地方空间等。因而暴发了“Linux没有四线程机制“”这一说法,可是精神上来说,并不是Linux没有多线程机制,只是其完成格局和其余操作系统差距而已。

那是私家在读书《Linux内核设计与落到实处》时候的某些感受,里面参预了有些友好关于操作系统的了解,对协调的水土保持的学问展开梳理,如有错误敬请指正。

  9)调用 alloc_pid() 为新进度分配一个一蹴而就的 PID;

在上述函数中,pid_t为隐含类型,实际上就是一个int的类型。隐含类型只数据类型的物理表示是不解的要么是不相干的

10)根据传递给clone() 的参数标志,copy_process()
拷贝或共享打开的公文、文件系统新闻、信号处理函数、进度地址空间和命名空间等;

二、完结过程和界别

11)做一些截止工作并赶回一个指向子进程的指针。

1、Linux是通过_cloen()系统调用来兑现fork()的,这一调用经过一名目繁多的参数标志来指明父子进度须求的资源。Fork(),vfork(),_cloen()库函数都依据各自要求的参数标志去调用cloen(),然后由cloen()去调用do_fork()函数,do_fork()函数也就是当真的创设进度的函数。他不负众望了创办进程的大部干活。同时他还会调用copy_process()函数。然后让进度开端运行。

12)回到 do_fork() 函数,如果 copy_process()
函数成功再次来到,新创造的子进度将被唤起并让其投入运作。

2、vfork()和fork()的机能雷同,除了不拷贝父进度的页表项,也就是说不会复制和父进程相关的资源,父子进程将共享地址空间,子进度对虚拟内存空间的别样实际修改实际上是在改动父进度虚拟内粗空间的情节。并且其父进度会被打断,直到子进度退出或者执行exec()函数族.那样由于父子进程共享地址空间,避免了fork在资源复制是的消耗。

  上边用一段不难的代码演示一下 fork() 函数:

3、写时copy机制:Linux系统选用了“些操作时复制”的方法,其是一种延迟资源复制的法子,子进度在成立的时候并不复制父进度的连锁资源,父子进程经过拜访同一的物理内存来伪装已经落实了的对资源的复制。那种共享是制度办法是只读方式,那点与vfork是见仁见智的,当子进度对内存数据存在那一个的操作时,才会进香资源的复制。正是出于那种机制的出现,vfork()好像已经没有何意义了。

  1 #include <unistd.h>
  2 #include <stdio.h>
  3 
  4 int main(){
  5     pid_t fpid;
  6     int count= 0;
  7     fpid = fork();              // fpid 为fork()的返回值
  8     if(fpid < 0){               // 当fork()的返回值为负值时,表明调用 fork() 出错
  9         printf("error in fork!");
 10     }
 11     else if(fpid  == 0){        // fork() 返回值为0,表明该进程是子进程
 12         printf("this is a child process, the process id is %d\n",getpid());
 13         count++;
 14     }
 15     else{                       // fork() 返回值大于0,表明该进程是父进程,这时返回值其实是子进程的PID
 16         printf("this is a father process, the process id is %d\n",getpid());
 17         count++;
 18     }
 19     printf("计数 %d 次\n",count);
 20     return 0;                                                                          
 21 }

三、在进度窗创设进度中copy_process()函数完结的干活//摘自:Linux内核设计与已毕

出口结果:

1、调用dup_task_struct()为心进程创建一个内核栈、thread_info结构和task_struct,这么些值与当前经过的值相同。此时,子进度和父进度的描述符完全相同。

澳门金沙国际 5

2、检查新创制的这几个子进度后,当前用户所用有的经过数目没有领先给他分配的资源的限量

  可以见到,调用 fork()
函数后,原本只有一个经过,变成了五个经过。那四个经过除了 fpid
的值不一致外大致完全相同,它们都继续执行接下去的顺序。由于 fpid
的值分歧,因而会进去分化的判定语句,那也是干什么多少个结实有分化之处的来由。其余,可以见到,父进度的
PID 刚好比子进程的 PID 小1。 fork()  的重返值有以下三种:

3、现在,子进度发轫使自己与父进程不一样开来,进度描述符内的过多成员都要被清0或者设置开头值。进度描述符的成员值并不是持续而来的,而根本是总括新闻,进度描述符中的大部数据都是共享的。

a)在父进度中,fork() 重回新创设子进度的 PID;

4、接下去,子进度的景色被装置为不可终端等待状态以有限支撑她不会投入运行

b)在子进程中,fork() 重返0;

5、copy_process()调用copy_flags()以更新task_struct的flags成员。讲明进度是负有最佳用户权限的PF_SUPER[RIV标志被清0。申明进度还没有调用exec()函数的PE_FORKNOEXEC标志被安装。

c)假使 fork() 调用出错,则赶回负值

6、调用get_pid()为新进度取得一个可行的PID

 

7、依据传给cloen()的参数标志copy_process()拷贝或者共享打开的文件、文件系统信、信号处理函数、进程地址空间和命名空间等。

 2、exec() :fs/exec.c (源程序
exec.c 达成对二进制可执行文件和 shell 脚本文件的加载与执行)

8、让父进度和子进度平分剩余的时间片。

  平时,创制新的历程都是为着及时实施新的、差距的顺序,而随后调用
exec() 那组函数就可以创造新的地址空间,并把新的程序载入其中

9、最后,copy_proccess()做扫尾工作并赶回一个只想子进度的指针。

  exec() 并不是一个函数,而是一个函数簇,一共包括两个函数,分别为:
execl、execlp、execle、execv、execvp、execve,定义如下:

 

#include <unistd.h>  

int execl(const char *path, const char *arg, ...);  
int execlp(const char *file, const char *arg, ...);  
int execle(const char *path, const char *arg, ..., char *const envp[]);  
int execv(const char *path, char *const argv[]);  
int execvp(const char *file, char *const argv[]);  
int execve(const char *path, char *const argv[], char *const envp[]);  

 

  这六个函数的效果实在差不多,只是接受的参数分裂。exec()
函数的参数紧要有3个部分:执行文书部分、命令参数部分和环境变量部分:

连锁函数:fork, execle, execlp, execv, execve, execvp
表头文件:#include 
函数定义:int execl(const char *path, const char *arg, …);
函数表达:execl()用来进行参数path字符串所表示的文书路径,
接下来的参数代表履行该文件时传递的argv[0],argv[1]…..是后一个参数必须用空指针NULL作了结
回来值   :成功则不重返值, 败北再次来到-1, 退步原因存于errno中
错误代码:参execve()
范例:

1)执行文书部分:也就是函数中的 path
部分,该部分提出了可执行文件的寻找方法。其中
execl、execle、execv、execve的摸索方法都是行使的相对路径,而
execlp和execvp则可以只交付文件名展开检索,系统会从环境变量
“$PATH”中寻找相应的门道;

 

2)命令参数部分:也就是函数中的 file
部分,该有的提议了参数的传递形式以及要传递哪些参数。那里,”l”结尾的函数表示使用各种列举的艺术传递参数;”v”结尾的象征将装有参数全部布局成一个指针数组举行传递,然后将该数组的首地址当做参数传递给它,数组中的最终一个指南针须要为
NULL;

 

3)环境变量部分:exec()
函数簇使用了系统默许的环境变量,也得以流传指定的环境变量。其中 execle
和execve 那多少个函数就足以在 envp[] 中指定当前经过所选取的环境变量。

  1. [   
  2. /*  执行 /bin/ls  -al  /ect/passwd */  
  3.   
  4. #include <unistd.h>   
  5. /**  
  6. * File: execl.c  
  7. *  
  8. */  
  9. main()   
  10. {   
  11.     execl(“/bin/ls”, “ls”, “-al”, “/etc/passwd”, (char *) 0);
      
  12. }    

·  当 exec() 执行成功时,exec()
函数会取代执行它的经过,此时,exec() 函数没有重临值,进程截至。当 exec()
函数执行破产时,将回来战败音讯(重返-1),过程继续执行前边的代码。

  经常,exec() 会放在 fork()
函数的子进度部分,来替代子进度继续执行,exec()
执行成功后子进度就会破灭,但是实施破产以来,就必要求使用 exit()
函数来让子进度退出。上面用一段不难的代码来演示一下 exec()
函数簇中的一个函数的用法,其他的参阅:

  1 #include <unistd.h>
  2 #include <stdio.h>
  3 #include <errno.h>
  4 #include <string.h>
  5 
  6 int main(){
  7     int childpid;
  8     pid_t fpid;
  9     fpid = fork();
 10     if(fpid == 0){                          // 子进程
 11         char *execv_str[] = {"ps","aux",NULL};      // 指令:ps aux 查看系统中所有进程 
 12         if( execv("/usr/bin/ps",execv_str) < 0 ){
 13             perror("error on exec\n");
 14             exit(0);
 15         }
 16     }
 17     else{
 18         wait(&childpid);
 19         printf("execv done\n");
 20     }
 21 }

 

   在那几个程序中,使用 fork() 创制了一个子历程,随后立即调用 exec()
函数簇中的 execv() 函数,execv()
函数执行了一条指令,显示当前系统中保有的长河,结果如下(进程有好多,那里只截了一有些):

澳门金沙国际 6

澳门金沙国际 7

  注意看最后四个经过,分别是父进度和调用 fork() 后创设的子进度。

 

二、进程终结

  进度被创建后,最后要停止。当一个经过终结时,内核必须自由它所占据的资源,并把这一音讯告诉其父进程。系统经过
exit() 系统调用来拍卖终止和退出进程的连带工作,而一大半干活则由
do_exit() 来完成 (kernel/exit.c):

1)将task_struct 中的标志成员设置为 PF_EXITING;

2)调用 del_timer_sync()
删除任一内决定时器,以担保没有定时器在排队,也没有定时器处理程序在运作;

3)调用 exit_mm() 函数释放进程占用的
mm_struct,假诺没有其余进度使用它们(地址空间被共享),就干净释放它们;

4)调用 sem__exit() 函数,假诺经过排队等候 IPC 信号,它则离开队列;

5)调用 exit_files() 和
exit_fs(),以个别递减文件描述符、文件系统数据的引用计数,若其中某个引用计数的值降至零,则表示从没经过使用相应的资源,能够释放掉进度占用的文件描述符、文件系统资源;

6)把 task_struct 的 exit_code 成员设置为经过的再次来到值;

7)调用 exit_notify() 向父进度发送信号,并把进度情状设置为
EXIT_ZOMBIE;

8)调用 schedule() 切换来新的经过,继续执行。由于 EXIT_ZOMBIE
状态的进度不会被再调度,所以那是进程所执行的终极一段代码, do_exit()
没有再次来到值。

  至此,与经过相关联的持有资源都被保释掉了,进度不可运行并处在
EXIT_ZOMBIE
退出状态。此时,进程本身所占有的内存还未曾自由,如内核栈、thread_info
结构和 task_struct
结构等,它存在的意义是向父进度提供音讯,当父进度收到音信后,或者通知内核那是井水不犯河水的新闻后,进度所具备的多余的内存将被放走。父进度可以由此wait4()
系统调用查询子进度是或不是为止,那实际使得进度具有了守候特定进度执行完结的能力。

  系统通过调用 release_task() 来释放进程描述符。

 

相关文章