精华内容
下载资源
问答
  • Linux多进程 -- 创建子进程

    千次阅读 2017-07-09 13:41:57
    Linux多进程创建子进程 fork函数 Linux创建单个子进程 Linux创建多个子进程 父子进程共享内容

    Linux多进程 – 创建子进程

    Linux创建单个子进程

    fork函数

    #include <unistd.h>
    pid_t fork(void)

    函数功能:创建一个子进程

    函数返回:一次函数调用,由一个进程变成两个进程。两个进程分别对fork做返回。

    1.返回子进程的pid(大于0) 父进程

    2.返回0 子进程

    fork函数返回值图解

    案例代码:demo1.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(void)
    {
            pid_t pid;
    
            printf("----------\n");
    
            pid = fork();              // 产生子进程
            if(-1 == pid)
            {
                    perror("fork error");
                    exit(1);
            }
            else if(0 == pid)         // 子进程
            {
                    printf("I am child process, pid=%u, ppid=%u\n", getpid(), getppid());
    
            }
            else                     // pid>0 父进程
            {
                    printf("I am parent process, pid=%u, ppid=%u\n", getpid(), getppid());
                    sleep(1);       // 睡眠一秒让子进程先结束
            }
    
            return 0;
    }
    

    创建多个子进程

    案例代码:demo2.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(void)
    {
        int i = 0;
        pid_t pid = 0;
    
        for(i = 0; i < 5; ++i)
        {
            pid = fork();
            if(-1 == pid)        // 创建子进程失败
            {
                perror("fork error");
                exit(1);
            }
            else if(0 == pid)    // 子进程 跳出循环
            {
                break;           
            }
        }   
    
        if(i < 5)
        {
            // 打印子进程信息
            sleep(i);
            printf("I am %dth child process, pid=%u, ppid=%u\n", i+1, getpid(), getppid());
        }
        else
        {
            sleep(i);
            printf("I am parent\n");
        }
    
        return 0;
    }
    

    父进程和子进程都是进程,公平竞争CPU执行资源。

    进程共享

    刚fork之后

    父子进程之间的相同之处:

    • 全局变量
    • .data 、.text
    • 堆 、栈
    • 宿组目录 、工作目录
    • 环境变量
    • 用户ID
    • 信号处理方式

    (以下均是重点知识)

    父子进程之间的不同之处:重点

    • 进程ID (父子进程之间拥有独立的PCB进程控制块)
    • fork返回值 (fork成功,父进程返回值大于0,子进程返回值等于0)
    • 父进程ID
    • 进程运行时间 (子进程的运行时间是从fork位置开始向下运行)
    • 闹钟(定时器) (每一个进程都有一个独有的定时器)
    • 未决信号集

    父子进程间遵循读时共享,写时复制的原则。

    注意规避全局变量共享问题。父子进程间全局变量是相同的,但是遵循读时共享写时复制的原则,一旦全局变量发生了改变,那么就会拷贝一份,所以不能使用全局变量进行进程间通信。

    父子进程共享:

    1.文件描述符(打开文件的结构体)

    2.mmap建立的映射区

    展开全文
  • LinuxLinux进程创建与管理

    万次阅读 多人点赞 2018-07-27 19:21:29
    Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建,新创建的进程叫做子进程,而创建子进程的...Linux进程创建的过程示意图如下所示:   子进程的创建 在Li...

    在Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建,新创建的进程叫做子进程,而创建子进程的进程叫做父进程。那个在系统启动及完成初始化之后,Linux自动创建的进程叫做根进程。根进程是Linux中所有进程的祖宗,其余进程都是根进程的子孙。具有同一个父进程的进程叫做兄弟进程。

    Linux进程创建的过程示意图如下所示:

     

    子进程的创建

    在Linux中,父进程以分裂的方式来创建子进程,创建一个子进程的系统调用叫做fork()。

    系统调用fork()

    为了在一个进程中分裂出子进程,Linux提供了一个系统调用fork()。这里所说的分裂,实际上是一种复制。因为在系统中表示一个进程的实体是进程控制块,创建新进程的主要工作就是要创建一个新控制块,而创建一个新控制块最简单的方法就是复制。

    当然,这里的复制并不是完全复制,因为父进程控制块中某些项的内容必须按照子进程的特性来修改,例如进程的标识、状态等。另外,子进程控制块还必须要有表示自己父进程的域和私有空间,例如数据空间、用户堆栈等。下面的两张图就表示父进程和相对应的子进程的内存映射:

    稍微介绍一下进程地址空间:

    进程的数据区也就是未初始化的数据(.bss)、已初始化的数据(.data);进程的栈区也就是进程的用户栈和堆;进程程序代码就是进程的程序文件(.text);除此之外还有进程的系统堆栈区。可见下面的这张更详细的介绍图:

    例子:

    #include <stdio.h>
    
    int count1=0;
    int main(void)
    {
        int pid;
        int count2=0;
        count1++;
        count2++;
        printf("count1=%d,count2=%d\n",count1,count2);
    
        pid=fork();
        count1++;
        count2++;
        printf("count1=%d,count2=%d\n",count1,count2);
        printf("pid=%d\n"pid);
    
        return 0;
    }

    程序运行结果为:

    由此可知:

    • 函数fork()却是分裂出了两个进程:因为自函数fork()之后执行了两遍之后的代码(先子进程一次,后父进程一次)。同时,这也证明了父进程和子进程运行的是同一个程序,也正是这个理由,系统并未在内存中给子进程配置独立的程序运行空间,而只是简单地将程序指针指向父进程的代码;
    • 两个进程具有各自的数据区和用户堆栈,在函数fork()生成子进程时,将父进程数据区和用户堆栈的内容分别复制给了子进程。同时,接下来的内容,父进程和子进程都是对自己的数据区和堆栈中的内容进行修改运算了。

    其实,在父进程中调用fork()之后会产生两种结果:一种为分裂子进程失败,另一种就是分裂子进程成功。如果fork()失败,则返回-1,;否则会出现父进程和子进程两个进程,在子进程中fork()返回0,在父进程中fork()返回子进程的ID。系统调用fork()工作流程示意图如下:

    也就是说,在fork()函数之前需要确认内核中有足够的资源来完成。如果资源满足要求,则内核slab分配器在相应的缓冲区中构造子进程的进程控制块,并将父进程控制块中的全部成员都复制到子进程的控制块,然后再把子进程控制块必须的私有数据改成子进程的数据。当fork()返回到用户空间之前,向子进程的内核栈中压入返回值0,而向父进程内核堆栈压入子进程的pid。最后进行一次进程调度,决定是运行子进程还是父进程。

    显然,为了能够使子程序和父程序执行不同的代码,在fork()之后应该根据fork()的返回值使用分支结构来组成程序代码。例如:

    #include <stdio.h>
    #include <sys/types.h>
    
    int main(void)
    {
        pid_t pid;
    
        pid = fork();
        if(pid < 0){
            ...                //打印fork()失败信息
        }else if(pid == 0){
            ...                //子进程代码
        }else{
            ...                //父进程代码
        }
    
        return 0;
    }

    在代码中获得当前进程pid的函数为:getpid();

    在代码中获得当前进程父进程pid的函数为:getppid()。

    这里需要注明一点:父子进程的调度的顺序是由调度器决定的,与进程的创建顺序无关。

    关于子进程的时间片

    与UCOSIII不同,Linux在大多数情况下是按时间片来进行进程调度的。当某一个进程所拥有的时间片用完之后,内核会立即剥夺它的运行权,停止它的执行。那么当子进程被创建出来之后,这个子进程的初始时间片应该是多大呢?

    Linux规定,如果父进程只是如之前那样简单地创建了一个子进程,那么系统会将父进程剩余的时间片分成两份,一份留给父进程,另一份作为子进程的时间片。因为之前的这种简单方式下,父子进程使用的是同一个代码,还没有成为两个真正的各自独立的进程,所以没有资格享用两份时间片。

     

    与进程相关的系统调用

    函数execv()

    为了在程序运行中能够加载并运行一个可执行文件,Linux提供了系统调用execv()。其原型为:

    int execv(const char* path, char* const argv[]);

    其中,参数path为可执行文件路径,argv[]为命令行参数。

    如果一个进程调用了execv(),那么该函数便会把函数参数path所指定的可执行文件加载到进程的用户内存空间,并覆盖掉原文件,然后便运行这个新加载的可执行文件。

    在实际应用中,通常调用execv()的都是子进程。人们之所以创建一个子进程,其目的就是执行一个与父进程代码不同的程序,而系统调用execv()就是子进程执行一个新程序的手段之一。子进程调用execv()之后,系统会立即为子进程加载可执行文件分配私有程序内存空间,从此子进程也成为一个真正的进程。

    如果说子进程是父进程的“儿子”,那么子进程在调用execv()之前,它所具有的单独用户堆栈和数据区也仅相当于它的私有“房间”;但因它还没有自己的“住房”,因此也只能寄住在“父亲”家,而不能“自立门户”,尽管它有自己的“户口”(进程控制块)。

    调用execv()后,父进程与子进程存储结构的示意图如下:

    与上文刚fork()的内存映像图相比,刚调用fork()之后,父子共同使用同一块程序代码;而调用execv()之后,子程序拥有了自己的程序代码区。

    例子:

    #include <stdio.h>
    #include <sys/types.h>
    
    int main(void)
    {
        pid_t pid;
    
        if(!(pid=fork())){
            execv("./hello.o",NULL);
        }else {
            printf("my pif is %d\n", getpid());
        }
    
        return 0;
    }

    而可执行文件./hello.o可以编写一个.c程序再进行编译获得。

    函数execv()其实是Linux的exec函数族成员之一,该函数族一共有5个函数和1个系统调用,分别是:

    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, const char* argv[]);
    int execvp(const char* file, const char* argv[]);
    int execve(const char* path, const char* argv[], char* const envp[]);

    其中,只有execv()是真正意义上的系统调用,其他的都是在此基础上经过包装的库函数。

    execv函数族的作用是,根据指定的文件名找到可执行文件,并将其关联到调用exec族函数的进程,从而使进程执行该可执行文件。简单地说,就是用execv族函数加载的程序文件代替该进程原来的程序文件。

    与一般的函数不同,exec族函数执行成功后一般不会返回调用点,因为它运行了一个新的程序,进程的代码段、数据段和堆栈等都已经被新的数据所取代,只留下进程ID等一些表面信息仍保持原样,虽然还是旧的躯壳,但是其实质内容已经全部变化了。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。

    系统调用wait()

    虽然子进程调用函数execv()之后拥有自己的内存空间,称为一个真正的进程,但由于子进程毕竟由父进程所创建,所以按照计算机技术中谁创建谁负责销毁的惯例,父进程需要在子进程结束之后释放子进程所占用的系统资源。

    为实现上述目标,当子进程运行结束后,系统会向该子进程的父进程发出一个信息,请求父进程释放子进程所占用的系统资源。但是,父进程并没有准确的把握一定结束于子进程结束之后,那么为了保证完成为子进程释放资源的任务,父进程应该调用系统调用wait()。

    如果一个进程调用了系统调用wait(),那么进程就立即进入等待状态(也叫阻塞状态),一直等到系统为本进程发送一个消息。在处理父进程与子进程的关系上,那就是在等待某个子进程已经退出的信息;如果父进程得到了这个信息,父进程就会在处理子进程的“后事”之后才会继续运行。

    也就是说,wait()函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

    如果父进程先于子进程结束进程,则子进程会因为失去父进程而成为“孤儿进程”。在Linux中,如果一个进程变成了“孤儿进程”,那么这个进程将以系统在初始化时创建的init进程为父进程。也就是说,Linux中的所有“孤儿进程”以init进程为“养父”,init进程负责将“孤儿进程”结束后的资源释放任务。

    这里区分一下僵尸进程和孤儿进程:

    • 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作;
    • 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程(也就是进程为中止状态,僵死状态)。

    Linix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

    参考文章:孤儿进程与僵尸进程[总结]

    系统调用exit()

    系统调用exit()用来终结一个进程,通常这个系统调用会在一些与进程控制有关的系统调用中被调用。

    如果一个进程调用exit(),那么这个进程会立即退出运行,并负责释放被中止进程除了进程控制块之外的各种内核数据结构。这种只剩下“身份证”的进程叫做“僵尸进程”,其进程控制块域state的值为TASK_ZOMBLE。

    一个因调用exec函数族函数而执行新的可执行文件的子进程,当进程执行结束时,便会执行系统调用exit()而使自己成为一个“僵尸进程”,这也正是父进程要负责销毁子进程的进程控制块的原因。

    另外,exit()执行到最后,将调用进程调度函数schedule()进行一次进程调度。

    这里区分一下exit()和_exit()函数:

    exit()定义在stdlib.h文件,_exit()定义在unistd.h文件中。同时两者的步骤也是不一样的:

    具体体现在哪里呢?

    在Linux标准库中,有一套称为高级I/O函数,例如我们所熟知的printf、fopen、fread、fwrite都在此列,它们也被称为缓冲I/O。其特征是对应每一个打开的文件,都存在一个缓冲区,在每次读文件时会多读若干条记录,这样下次读文件时就可以直接从内存的缓冲区去读。在每次写文件时也会先写入缓冲区,当缓冲区写满,或者我们fflush()手动的刷新缓冲区,或者遇到\n、EOF这样的结束符,才会把对应的数据从缓冲区写入到文件中。这样的好处是大大增加的文件的读写的速度,因为我们都知道磁盘上的读写速度是很慢的,但这也为我们编程带来了一点麻烦,例如有些数据,我们认为已经写入了文件,但实际上它们很可能存在在缓冲区中。

    也就是说,_exit()函数的作用是:直接使进程停止运行,清除其使用的内存空间,并清除其在内核的各种数据结构;exit()函数则在这些基础上做了一些小动作,在执行退出之前还加了若干道工序。exit()函数与_exit()函数的最大区别在于exit()函数在调用exit  系统调用前要检查文件的打开情况,把文件缓冲区中的内容写回文件。也就是图中的“清理I/O缓冲”。

    参考文章:exit函数和_exit函数的区别

    系统调用vfork()

    vfork()是Linux提供的另一个用来生成一个子进程的系统调用。

    与fork()不同,vfork()并不把父进程全部复制到子进程中,而只是用用赋值指针的方法使子进程与父进程的资源实现共享。由于函数vfork()生成的子进程与父进程共享同一块内存空间,因此实质上vfork()创建的是一个线程,但习惯上人们还是叫它子进程。

    用vfork()创建子进程且调用execve()之后,子进程的内存映像如下所示:

    这里区分一下fork()与vfork():

    • fork():子进程拷贝父进程的数据段,代码段 。vfork():子进程与父进程共享数据段 ;
    • fork():父子进程的执行次序不确定 。vfork():保证子进程先运行,在调用execve()或exit()之前,与父进程数据是共享的,在它调用execve()或exit()之后,父进程才可能被调度运行。

    注意:由于vfork()保证子进程先运行,在它调用execve()或exit()之后,父进程才可能被调度运行。如果在调用这两个函数之前,子进程依赖于父进程的进一步动作,则会导致死锁。 

     

    内核中的进程和线程

    操作系统是一个先于其他程序运行的程序,那么当系统中除了操作系统之外没有其他的进程时怎么办?同时,系统中的所有用户进程必须由父进程来创建,那么“祖宗进程”是什么呢?

    前一个问题由Linux的进程0来解决,后一个问题由Linux的进程1来解决。其实,前者是一个内核进程,而后者先是一个内核进程,后来又变为用户进程。

    内核线程及其创建

    Linux实现线程的机制非常独特,它没有另外定义线程的数据结构和调度算法,只不过在创建时不为其分配单独的内存空间。如果线程运行在用户空间,那么它就是用户线程;如果运行在内核空间,那么它就是内核线程。并且,无论是线程还是进程,Linux都用同一个调度器对它们进行调度。

    但是Linux却单独提供了一个用于创建内核线程的函数kernel_thread()。该函数的原型如下:

    int kernel_thread(int (* fn)(void *), void* arg, unsigned long flags);

    其中,fn指向线程代码;arg为线程代码所需的入口参数。

    内核线程周期性执行,通常用来完成一些需要对内核幕后完成的任务,例如磁盘高速缓存的刷新、网络连接的维护和页面的交换等,所以它们也叫做内核任务。

    进程0

    计算机启动之后,首先进行Linux的引导和加载,在Linux内核加载之后,初始化函数start_kernel()会立即创建一个内核线程。因为Linux对线程和进程没有太严格的区分,所以就把这个由初始化函数创建的线程叫做进程0。

    进程0的执行代码时内核函数cpu_idel(),该函数中只有一条hlt(暂停)指令;也就是说,这个进程什么工作也没做,所以也叫做空闲进程。该空闲进程的PCB叫做init_task(),当系统没有可运行的其它进程时,调度器才会选择进程0来运行。

    进程1

    进程1也叫做init进程,它是内核初始化时创建的第2个内核线程,其运行代码为内核函数init()。

    该函数首先创建kswapd()等4个与内存管理有关的内核线程。接下来,在内核的init()中调用execve()系统调用装入另一个需要在用户空间运行的init函数代码,于是进程1就变成一个普通的进程。它是用户空间的第一个进程,所以它就成了其他用户进程的根进程。

    只要系统,init进程就永不中止,它负责创建和监控操作系统外层所有进程的活动。

    守护进程

    守护进程是不受终端控制并在后台运行的进程。Linux使用了很多守护进程,在后台做一些经常性的注入定期进行页交换之类的例行工作。

    守护进程一般可以通过以下方式启动:

    • 在系统启动时由启动脚本启动,这些启动脚本通常放在/etc/rc.d目录下;
    • 利用inetd超级服务器启动,大部分网络服务都是这样启动的,如ftp、telnet等;
    • 由cron定时启动以及在终端用nohup启动的进程也是守护进程。

     

    展开全文
  • Linux 进程创建多进程

    万次阅读 2012-08-12 20:32:50
    说到进程,首先要明确的一个概念就是什么是进程进程是“a program in execution”。一个进程由如下元素组成: –程序的上下文(context),它是...本文讨论创建进程 最近本人写了一个关于进程操作的程序,之前对进

    说到进程,首先要明确的一个概念就是什么是进程,进程是“a program in execution”。一个进程由如下元素组成:

    –程序的上下文(context),它是程序当前执行的状态

    –程序的当前执行目录

    –程序访问的文件和目录

    –程序的信任状态或者说访问权限,比特它的文件模式和所有权

    –内存和其他分配给进程的系统资源

    本文讨论创建进程

    最近本人写了一个关于进程操作的程序,之前对进程有过了解,但是,没有写程序,所以总感觉是徘徊在门外,很多东西只是了解个大概,在这次真的要写程序的时候,不知道如何下手。

    1、  system函数

    在头文件#include<stdlib.h>中包含

    函数原型: int system(const char *command);

    功能描述:  system()通过调用/bin/sh –c  command来执行具体的command命令,在command完成后返回。在command执行期间,SIGCHLD信号被阻塞,SIGINTSIGQUIT将被忽略。

    返回值:    如果没有找到/bin/shsystem返回127

                                如果出现其他错误则返回-1

                                如果执行成功则返回command的代码。

                                但是如果commandNULLsystem返回一个非0值,否则返回0

    示例:

     

    2、  fork系统调用

    fork调用创建一个新的进程。新的进程或者说子进程是调用进程的或者说父进程的副本。

    Fork的语法是

    #include<unistd.h>

    pid_t fork(void);

    如果fork执行成功,就向父进程返回子进程的PID,并向子进程返回0。这就一起这即使你只调用fork一次,他也会返回两次。

    Fork创建的新进程是和父进程(除了PIDPPID)一样的副本,包括真实和有效的UIDGID、进程组合会话ID、环境、资源限制、打开的文件以及共享内存段。

    父进程和子进程之间有一点区别。子进程没有继承父进程的超市设置(使用alarm调用

    )父进程创建的文件锁,或者未决信号。要理解的关键概念是fork创建的新进程是父进程的一个准确副本。

     

    2fork系统调用

    fork调用创建一个新的进程。新的进程或者说子进程是调用进程的或者说父进程的副本。

    Fork的语法是

    #include<unistd.h>

    pid_t fork(void);

    如果fork执行成功,就向父进程返回子进程的PID,并向子进程返回0。这就一起这即使你只调用fork一次,他也会返回两次。

    Fork创建的新进程是和父进程(除了PIDPPID)一样的副本,包括真实和有效的UIDGID、进程组合会话ID、环境、资源限制、打开的文件以及共享内存段。

    父进程和子进程之间有一点区别。子进程没有继承父进程的超市设置(使用alarm调用)父进程创建的文件锁,或者未决信号。要理解的关键概念是fork创建的新进程是父进程的一个准确副本。

    示例:

    #include<unistd.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    int main(void)
    {
    	pid_t child;
    	if((child = fork()) == -1)
    	{
    		perror("fork");
    		exit(EXIT_FAILURE);
    	}
    	else if(child == 0)					//子进程中
    	{
    		puts("in child");
    		printf("\tchild pid = %d\n",getpid());
    		printf("\tchild ppid = %d\n",getppid());
    		exit(EXIT_SUCCESS);
    	}
    	else	
    	{
    		puts("in parent");
    		printf("\tparent pid = %d\n",getpid());
    		printf("\tparent ppid = %d\n",getppid());
    	}
    	exit(EXIT_SUCCESS);
    }
    


     

    3、  exec函数族

    fork函数一样,exec也在<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 *constargv[]);

    int execvp(const char *file, char *constargv[]);

    int execve(const char *path, char *constargv[], char *const envp[]);

     

    exec用被执行的程序完全替换了调用进程的映像。Fork创建了一个新进程就产生了一个新的PIDexec启动一个新程序,替换原有进程。因此被执行的进程的PID不会改变。

    例如:

    int execve(const char *path, char *constargv[], char *const envp[]);

    接收三个参数:

    path是要执行的二进制文件或脚本的完整路径。

    argv是要传递给程序的完整参数列表,包括argv[0],它一般是执行程序的名字,

    envp是指向用于执行execed程序的专门环境的指针。

     

    这几个函数可以简单讨论如下:

    名字中含有l的函数:希望接受以逗号分隔的参数列表,列表以NULL指针作为结束标志,这些参数将传递给被执行的程序。

    名字中包v的函数:则接受一个向量,也就是以空结尾的字符串的指针数组。这个数组必须以一个NULL指针作为结束标志。

    不过,需要注意的是,有时候可能那个NULL,需要写成(char *)0

     

    //一个创建num个进程的示例:
    //其中batchscript是已经写好的shell脚本文件。
    void createsubprocess(int num)
    {
    	int i;
    	int child;
    	int pid[num];	
    	for(i=0;i<num;i++)
    	{
    		if((child = fork()) == -1)
    		{
    			perror("fork");
    			exit(EXIT_FAILURE);
    		}
    		else if(child==0)		//子进程运行
    		{
    			pid[i]=getpid();
    			if(execl("/usr/audio/./batchscript","./batchscript",(char *)0) == -1 )
    			{
    				perror("execl");
    				exit(EXIT_FAILURE);
    			}
    		}
    		
    		else
    		{	
    		}   
    	}
    	for(i=0;i<num;i++)
    	{
    		waitpid(pid[i],NULL,0);
    				
    	}
    }
    


    实现了多进程并发。

     

     

     

     

     

     

    展开全文
  • Linux——进程创建和进程终止

    千次阅读 2021-03-15 21:05:48
    进程创建&进程终止1.进程创建1.1fork函数初识1.2 fork函数返回值1.3 写时拷贝2.进程终止 1.进程创建 1.1fork函数初识 fork函数,从已存在进程中创建一个新进程,新进程为子进程,而原进程为父进程 进程调用...

    1.进程创建

    1.1fork函数初识

    • fork函数,从已存在进程中创建一个新进程,新进程为子进程,而原进程为父进程
      在这里插入图片描述
    • 进程调用fork,当控制转移到内核中的fork代码后,内核会做如下几件事:
    • 分配新的内存块和内核数据结构给子进程
    • 将父进程部分数据结构内容拷贝至子进程
    • 添加子进程到系统进程列表当中
    • fork返回,开始调度器调度

    1.2 fork函数返回值(为什么两个返回值?)

    在这里插入图片描述

    1.3 写时拷贝(代码共享why和数据私有why)

    • 默认情况下,父子进程共享代码,数据各自私有一份
    • 代码共享:所有代码共享,一般都是从fork()函数之后开始执行,原因:代码不可以被修改,所以各自浪费私有空间
    • 数据私有原因:因为进程之间具有独立性,数据是很多的,但并不是所有的数据都立马要使用,且不是所有的数据都需要进行拷贝。如果需要立马独立,就要将数据全部拷贝,把本来可以再后面拷贝的,甚至不需要拷贝的数据全部拷贝,但是这样十分的浪费时间和空间
    • 拷贝的过程不是立马做的,父子代码共享,父子再不写入时,数据也是共享的,此时,如果任意一方试图写入,便以写时拷贝的方式各自一份副本,所以我们这里引入一个写时拷贝的概念
      在这里插入图片描述

    1.4 fork常规用法和调用失败的原因

    1.4.1 常规用法

    • 子进程赋值父进程,通过if、else来实现父子进程执行不同的代码段。比如父进程等待客户端请求生成子进程来进行处理
    • 一个进程要执行一个不同的程序。比如fork后,子进程调用exec函数

    1.4.2调用失败的原因

    • 系统中有太多的进程
    • 实际用户的进程数超过了限制

    1.5 如何理解子进程创建和fork()?

    • 子进程创建的实质是系统多了一个进程,因此OS需要将子进程也管理起来,就需要创建新的PCB,虚拟空间,页表,映射关系(这些属性自己进程都以父进程为模板拷贝过来)
    • 并且当父子进程各有一方想要写入数据时就会发生写时拷贝,将数据独立出来,比如说数据一共是10M,但写入的时候是1M,发生写时拷贝,此时拷贝的应该是1M的数据

    2.进程终止

    2.1进程退出场景

    在这里插入图片描述
    在这里插入图片描述

    2.2为什么main()函数的返回值通常是0呢?

    • 因为在C/C++中的函数设计通常把0表示为正确,当main()函数执行结束的时候返回0,表示这个函数正常运行完,并且结果正确,非0表示错误,每种退出码对应一种错误。

    2.3进程常见退出方法

    2.3.1正常终止

    • 从main函数返回
    • 调用库函数exit()
    • _exit(接口调用)

    2.3.2异常终止

    • ctrl+c,信号终止

    2.4 exit,_exit,return区别

    • exit:终止整个进程,任何地方调用,都会终止进程,会刷新缓冲区
      在这里插入图片描述
      在这里插入图片描述

    • _exit:属于系统调用,不会刷新缓冲区
      在这里插入图片描述
      在这里插入图片描述

    • return退出:return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做exit的参数

    展开全文
  • LINUX shell和进程创建

    千次阅读 2014-04-28 21:25:46
    一、在LINUX系统中需要用到shell来运行命令。那么shell到底是什么?  维基百科:Unix shell也叫做命令行界面,它是Unix操作系统下传统的用户和计算机的交互界面。用户直接输入命令来执行各种各样的任务。   ...
  • Linux进程进程创建

    千次阅读 2013-07-12 17:01:14
    今天学习了Linux进程创建的基本原理,是基于0.11版本核心的。下面对其作一下简单的总结。一、Linux进程在内存中的相关资源 很容易理解,Linux进程的创建过程就是内存中进程相关资源产生的过程,那么Linux进程在...
  • Linux shell实现多进程并发执行

    千次阅读 2019-08-15 11:57:10
    在bash中,使用后台任务来实现任务的“多进程化”。在不加控制的模式下,不管有多少任务,全部都后台执行。也就是说,在这种情况下,有多少任务就有多少“进程”在同时执行。我们就先实现第一种情况: 实例一:正常...
  • Linux Shell实现多进程并发执行

    万次阅读 2017-08-18 10:16:58
    在bash中,使用后台任务来实现任务的“多进程化”。在不加控制的模式下,不管有多少任务,全部都后台执行。也就是说,在这种情况下,有多少任务就有多少“进程”在同时执行。我们就先实现第一种情况: 实例一:正常...
  • linux 多进程

    千次阅读 2017-03-29 17:22:52
    Linux下的多进程编程初步Linux下的多进程编程初步 引言 多进程编程 1 Linux下进程的结构 2 Linux下的进程控制 21 僵尸进程 22 fork 23 exec 函数族 3 Linux下的进程间通信 31 管道 无名管道 有名管道 32 消息队列 33...
  • Linux】守护进程/精灵进程创建

    千次阅读 多人点赞 2021-04-12 21:18:08
    守护进程周期性的执行某些任务或者等待处理某些事件,Linux上的大多数服务器都是用守护进程实现的。 在系统启动中默认的守护进程的父进程ID全都是init,守护进程特征就是在程序运行名后加了一个d,但不是所有的尾...
  • Linux C多进程编程基础

    千次阅读 2018-08-20 23:15:51
    关于进程概念相关的内容请打开... Linux内核创建进程标号为0以及进程标号为1的进程。其中PID为1的进程是初始化进程init,Linux中的所有进程都是由其衍生而来的,在shell下执行程序启动的进程则是shell进程的子进...
  • 由于程序的需要,我们需要在一台服务器上启动个相似的子进程,如何做呢? 有两种可以尝试一下: 1.循环创建: #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <...
  • linux创建守护进程

    千次阅读 2014-10-12 12:27:40
     linux开启进程都是与终端绑定的,终端一关,进程也关,如果想独立不受干扰,必须将此进程变为守护进程(在后台运行,不以终端方式与用户交互)。 守护进程能够突破这种限制,它从被执行开始运转,直到整个系统...
  • linux多进程实现聊天室功能

    千次阅读 2014-05-18 17:12:09
    采用多进程的方式,实现聊天室功能。 客户端: 开启一个子线程,接受用户输入的信息。 主线程负责将用户的信息发送给服务器,并且接受并显示服务器发送过来的消息  服务器端: 每当有一个...
  • LInux进程创建过程

    千次阅读 2019-04-15 22:58:41
    传统的fork系统调用直接把所有的资源复制给新创建进程,但是这种实现过于简单,效率低下,因为并不支持拷贝数据的共享。 更糟的是如果新进程打算立即执行一个新的映像那么所有的拷贝都将前功尽弃。 Linux下面的...
  • mkfifo命令创建一个FIFO特殊文件,是一个命名管道(可以用来做进程之间通信的桥梁) 管道也是一种文件,一般是linux中的一个页大小,4k,管道数据一旦被读取就没了。 管道是单方向 mkfifo命令文档 使用命名管道下面...
  • 首先我们的问题是,线程是什么? 线程是进程中的一条执行流,是CPU执行调度的基本单位,一个进程中可以有多个线程。在Linux下,线程执行流是通过PCB实现的,且一个进程中可能有多个PCB,这些PCB共享...多线程与多进程
  • 这两天抽时间继续往下看了看 Linux 内核和 Unix 编程的书,边看边琢磨,想到个关于进程在 fork 子进程或 pthread 出 lwp 时父亲进程的栈段是如何处理的问题,结合 Linux 内核的说明对这个问题有了明确的理解,在此做...
  • 前面已讲过使用一个进程实现服务端和客户端P2P通信的实例,但是它只能同时处理一个... 使用多进程并发处理多个client请求以及实现P2P通信,父进程专门监听端口,每监听到一个连接就创建一个子进程处理这个客户端,于
  • Linux - 进程 (二) 进程创建

    千次阅读 2014-09-06 13:21:23
    Linux 进程系统介绍
  • Linux进程创建与修改

    千次阅读 2016-09-09 17:25:38
    函数fork()用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝,继承了了父进程整个进程的地址空间(代码段、堆栈段、数据段),包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、...
  • linux多进程的同步

    千次阅读 2011-10-18 14:37:42
    linux 多进程的同步:linux多进程实现同步操作,操作单个信号量已经不能实现,对多进程的通信可以采取信号集的方式,一个信号集包含了多个信号量。 首先通过semget()创建信号量。例如:semid = semget(SEMKEY,2...
  • Linux下C语言多进程实现

    千次阅读 2017-07-24 10:42:36
    这是一个简单的例子 #include #include //thread函数是开的一个进程实现的功能 void thread(void){  int i;  for(i=0;i  printf("this is a thread\n"); }  int main(){  pt
  • linux 操作系统 创建多个子进程

    千次阅读 2017-04-17 22:47:46
    1、编写一段程序实现以下...c) 父进程显示自己的进程ID和一些提示信息,然后调用waitpid()等待个子进程结束,并在子进程结束后显示输出提示信息表示程序结束。 2、 创建两个子进程的代码如下: #include #include
  • linux多进程/多线程文件操作详解

    万次阅读 2016-07-24 20:18:37
    内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响 1.每个进程进程表都有一个记录项,记录项中包含有一张打开文件描述符表,与文件描述符相关联的是: ...
  • Linux多进程编程

    千次阅读 2017-11-18 23:11:13
    什么是一个进程?进程这个概念是针对系统而不是针对用户的,对用户来说,他面对的概念是程序。当用户敲入命令执行一个程序的时候,对系统而言,它将启动一个进程。...多进程编程的主要内容包括进程控制和进程间通信
  • Linux Shell多进程并发以及并发数控制

    千次阅读 2018-06-30 18:22:01
    1.1. linux后台进程 Unix是一个任务系统,允许用户同时运行个程序。shell的元字符&amp;提供了在后台运行不需要键盘输入的程序的方法。输入命令后,其后紧跟&amp;字符,该命令就会被送往到linux后台...
  • linux实现进程同步

    千次阅读 2019-04-26 18:05:42
    网上的大部分教程讲的都是线程同步,却很少有关于进程同步的博客,但其实线程同步与进程同步还是有些许差别的,故写此博客加以说明. 知识点 1.linux semaphore 头文件#include <semaphore.h> 编译注意事项:...
  • 看了传智播客讲的linux视频,把“多进程进行socket编程”好好理解了一下,整理出来的。 用TCP协议编写了一个简单的服务器、客户端,其中服务器一直在监听本机8000号端口。如果收到客户端的链接,就在服务器端把...
  • [Linux]Linux Shell多进程并发以及并发数控制

    万次阅读 多人点赞 2016-09-12 12:56:59
    Unix是一个任务系统,允许用户同时运行个程序。shell的元字符&提供了在后台运行不需要键盘输入的程序的方法。输入命令后,其后紧跟&字符,该命令就会被送往到linux后台执行,而终端又可以继续输入下一个命令了...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 289,015
精华内容 115,606
关键字:

linux实现多进程创建

linux 订阅