多进程linux程序_多进程程序运行时,获取当前进程号 linux - CSDN
  • Linux多进程程序设计

    2016-08-01 20:36:14
    用到的函数的功能主要有获取进程id、创建进程进程退出、进程等待、执行程序。  获取进程id:getpid(),getppid()  创建进程:fork(),vfork()  进程退出:exit()  进程等待:wait()  执行程序:exec(),...

    用到的函数的功能主要有获取进程id、创建进程、进程退出、进程等待、执行程序。 
    获取进程id:getpid(),getppid() 
    创建进程:fork(),vfork() 
    进程退出:exit() 
    进程等待:wait() 
    执行程序:exec(),system()

    getpid函数

    原型:pid_t getpid(void)
    头文件:<unistd.h><sys/types.h>
    功能:返回调用该函数的进程id,且该函数总是运行成功

    getppid函数

    原型:pid_t getppid(void)
    头文件:<unistd.h><sys/types.h>
    功能:返回调用该函数的进程的父进程id,且该函数总是运行成功

    fork函数

    原型:pid_t fork(void)
    头文件:<unistd.h>
    功能:创建一个子进程。成功则在父进程返回子进程PID,在子进程返回0;失败返回-1.
    
    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    void main()
    {
        pid_t pid;
        pid=fork();
        if(pid==0)
        {
            printf("this is child program,the return is %d\n",pid);
        }
        else
        {
            printf("this is father program,the return is %d\n",pid);
        }
        exit(0);
    }

    这里写图片描述 
    使用fork函数后,父进程就会产生一个子进程,并且父进程和子进程都会执行fork函数后的代码,直至退出进程。由于在父进程返回子进程PID,在子进程返回0,因此可利用该返回值,让父进程和子进程执行不同功能。子进程打印“this is child program,the return is 0”在shell提示符后面的原因为先执行父进程,执行完父进程后操作系统弹出shell提示符,然后在执行子进程。

    vfork函数

    原型:pid_t vfork(void)
    头文件:<unistd.h><sys/types.h>
    功能:创建一个子进程,并阻塞父进程。成功则在父进程返回子进程PID,在子进程返回0;失败返回-1.
    
    #include<stdio.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<stdlib.h>
    void main()
    {
        pid_t pid;
        pid=vfork();
        if(pid==0)
        {
            printf("this is child program,the return is %d\n",pid);
        }
        else
        {
            printf("this is father program,the return is %d\n",pid);
        }
        exit(0);
    }

    这里写图片描述 
    先执行子进程,再执行父进程。 
    fork函数与vfork函数都用于创建子进程,但两者区别有两点: 
    ① fork:子进程拥有独立的数据段,堆栈。 
    vfork:子进程与父进程共享数据段,堆栈。 
    ② fork:父、子进程的执行次序不确定 
    vfork:子进程先运行,父进程后运行
    父进程可采用return或者exit()函数退出进程; 
    子进程只能采用exit()函数退出进程,其中exit(0)表示正常退出,exit(1)表示异常退出

    wait函数

    原型:pid_t wait(int *status)
    头文件:<sys/wait.h><sys/types.h>
    功能:挂起调用它的进程,直到该进程的一个子进程结束。成功则返回结束的那个子进程的ID,失败返回-1
    参数:status若不为NULL,则用于记录子进程的退出状态

    执行进程的函数为exec,exec是一个函数族,共有6个:execl,execv,execle,execve,execlp,execvp,以execl为例进行说明

    execl函数

    原型:int execl(const char* pathname,const char* arg,...)
    头文件:<unistd.h>
    功能:运行可执行文件,并且原进程的代码不再执行,只执行新的代码。成功时不返回,失败时返回-1.
    参数:pathname为要运行的可执行文件的路径
         arg及后面的参数作为可执行文件的参数,并以一个NULL作为参数的结束标志。
    
    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/wait.h>
    void main()
    {
        pid_t pid;
        pid=fork();
        if(pid==0)
        {
            execl("/bin/ls","ls","/home",NULL);
            printf("this is child program,the return is %d\n",pid);
        }
        else
        {
            wait(NULL);
            printf("this is father program,the return is %d\n",pid);
        }
        exit(0);
    }

    这里写图片描述 
    由上可看出,使用execl函数后,原进程代码不再执行,转为执行新的代码。 
    fork创建一个新的进程,产生一个新的PID。 
    exec保留原有的进程,执行新的代码。

    system函数

    原型:int system(const char* command)
    头文件:<stdlib.h>
    功能:执行一条shell指令,该函数会产生子进程





    展开全文
  • Linux C多进程编程基础

    2018-11-10 19:56:11
    关于进程概念相关的内容请打开链接,本文所介绍的是进程的POSIX标准。...其中PID为1的进程是初始化进程init,Linux中的所有进程都是由其衍生而来的,在shell下执行程序启动的进程则是shell进程的子进...

    关于进程概念相关的内容请打开链接,本文所介绍的是进程的POSIX标准。

    进程的关系

           Linux中的所有进程都是相互联系的,进程之间的的从属关系有父/子关系和兄/弟关系。

           Linux内核创建了进程标号为0以及进程标号为1的进程。其中PID为1的进程是初始化进程init,Linux中的所有进程都是由其衍生而来的,在shell下执行程序启动的进程则是shell进程的子进程。进程可以再启动一个或多个进程,这样就形成了一颗进程树,每个进程都是树中的一个节点,其中树根就是初始化进程init。

           按进程的从属关系,进程分为以下几种(假设一个进程为P):

           ·祖先进程p_opptr(original parent):指向创建了进程P的进程的进程描述符,如果父进程不存在(例如已被销毁或从父进程中退出),则指向进程init的进程描述符。所以当一个shell用户启动了一个后台进程并从shell退出的时候,这个后台进程将变成init的子进程。

           ·父进程p_pptr(parent):指向创建了进程P的进程的进程描述符,其值通常来说和p_opptr一致,但也可能不同。

           ·子进程p_cptr(child):指向由进程P创建的进程中年龄最小(即创建时间最晚)的进程的进程描述符,即上一次创建的进程。

           ·兄进程p_osptr(older sibling):指向与P进程同属于一个父进程,但创建时间比P进程早的进程进程描述符。

           ·弟进程p_ysptr(younger sibling):与兄进程相对,指向与P进程同属于一个父进程,但创建时间比P进程较晚的进程。

    进程的状态

           进程在其生存周期内可能处于以下状态中,且一个进程在同一时刻只能位于其中一个状态:

           ·可运行状态(TASK_RUNNING):占用处理器执行或准备执行。

           ·可中断的等待状态(TASK_INTERRUPTIBLE):进程被挂起或睡眠,当满足某些条件时才退出这种等待状态。这些条件包括:硬件中断、等待的资源被释放、传递一个信号灯,退出等待状态后会回到可运行态。

           ·不可中断的等待状态(TASK_UNINTERRUPTIBLE):和上一个状态相似,区别是当接收到信号时不能退出这个等待状态。

           ·暂停状态(TASK_STOPPING):进程的运行被暂停,通常来说是接收到SIGSTOP、SIGTTIN或者SIGTTOU信号后。如果一个进程被另外一个进程监控时,任何信号都可以把这个进程置于TASK_STOPPEN状态。

           ·僵尸状态(TASK_ZOMBIE):进程的执行已经被终止,但父进程还没有wait系列系统调用已返回的相应信息,此时内核不能丢弃与该进程有关的数据,因为父进程可能还需要这些数据。

           进程在这几种状态之间相互转化,但对于用户而言是透明的,这个切换的过程常被称为进程调度。进程是一个随执行过程不断变化的实体,和程序要包含指令和数据一样,进程也包含程序计数器和所有处理器寄存器的值,同时它的堆栈中存储着参数、返回地址以及变量之类的临时数据。在多处理机操作系统中,进程之间除了从属关系以外相对独立,如果系统中某个进程崩溃,不会影响到其余进程,每个进程运行在各自的虚拟地址空间中,通过一定的通信机制,它们之间才能发生联系。

    进程描述符(进程控制块)

           为了对进程进行管理,Linux内核必须了解每个进程当前的执行状态,这些状态包括进程的优先级、运行状态、分配的地址空间等。为了达到这个目的,Linux内核提供了一个结构体task_struct来描述进程(或者说表示进程实体)。

    struct task_struct {
        volatile long state;    //进程运行时的状态,-1表示不可运行,0表示可运行,大于0表示已停止
        unsigned int falgs;     //flags是进程当前的状态标识
                                //0x00000002表示进程正在被创建
                                //0x00000004表示进程正准备退出
                                //0x00000040表示进程被fork,但没有执行exec
                                //0x00000400表示此进程由于其他进程发送相关信号而被杀死
        unsigned int rt_priority    //进程的优先级
        truct list_head tasks;
        struct mm_struct *mm;    //内存的使用情况
        int exit_state;
        int exit_code, exit_signal;
        pid_t pid;    //Process ID
        pid_t tgid;    //进程组号
        struct task_struct *real_parent;    //该进程的创建者,即“亲生父亲”
        struct task_struct *parent;    //该进程现在的父进程,有可能是“继父”
        struct list_head children;    //指向该进程孩子的链表,可以得到所有子进程的进程描述符
        struct list_head sibling;    //指向该进程兄弟的链表,也就是其父进程的所有子进程
        struct task_struct *group_leader;    //进程组的组长
        struct list_head thread_group;    //该进程所有线程的链表
        time_t utime, stime;    //处理器相关的时间参数
        struct timespec start_time;    //进程启动时间
        struct timespec real_start_time;    //与上一条类似
        char comm[TASK_COMM_LEN];
        int link_count, total_link_count;    //文件系统信息计数
        struct thread_struct thread;
        struct fs_struct *fs;    //特定处理器下的状态
        struct files_struct *files;    //文件系统相关信息结构体
    
        //打开文件相关信息结构体
        struct signal_struct *signal;
        struct sighand_struct *sighand;
    
        //松弛时间值,用来规定select()和epoll()的超时时间,单位是纳秒
        unsigned long timer_slack_ns;
        unsigned long default_timer_slack_ns;
    };

    进程标识符

           进程标识符(Process ID)是进程描述符中最重要的组成部分,用于标识和对应唯一的进程。

           Linux内核使用了一个数据类型pid_t来存放进程标识符,这个数据类型实质上是一个机器相关的无符号整数(类似于size_t)。PID通常被顺序编号,且PID是可以重复使用的。当一个进程被回收之后,过一段时间,其标识符又可以被再次使用。为了和16位处理器架构的应用系统相兼容,在Linux内核上通常允许使用的进程标识符是0~32767。

           在Linux中,有如下几个特殊的进程标识符所对应的进程:

           ·PID0:对应的是交换进程(swapper),实际上并不存在,其用于执行多进程的调用(子进程返回0作为判断父子进程的标志)。

           ·PID1:初始化进程(init),在自举过程结束时由内核调用,其对应的文件是/sbin/init,负责Linux的启动工作,这个进程在系统运行过程中是不会终止的(守护进程),可以说当前操作系统中的所有进程都是由这个进程衍生而来的。

           ·PID2:可能对应页守护进程(pagedaemon),用于虚拟存储系统的分页操作。

    Linux进程的用户

           与文件类似的是,进程也有对应的实际用户ID、实际组ID、有效用户ID、有效组ID。对于这些用户而言每个进程同样存在一个相应的标识符,Linux提供了相应的函数用于获取这些标识符,对其标准调用格式说明如下:

    #include <unistd.h>
    #include <sys/types.h>
    
    uid_t getuid(void);    //获取实际用户ID
    uid_t geteuid(void);   //获取有效用户ID
    gid_t getgid(void);    //获取实际组ID
    gid_t getegid(void);   //获取有效组ID

    Linux进程操作

    创建进程

           POSIX标准定义了进程创建函数fork和vfork以创建一个新进程,被创建的新进程称为当前执行该创建函数进程的子进程。

    fork

           fork函数实质是一个系统调用,其作用是创建一个新的进程,当一个进程调用它完成后就出现两个几乎一模一样的进程,其中由fork创建的新进程被称为子进程,而原来的进程称为父进程。子进程是父进程的一个拷贝,即子进程从父进程得到了数据段和堆栈段的拷贝,这些需要分配新的内存;而对于只读的代码段,通常使用共享内存的方式访问。

           用户通常在有如下需求的时候使用fork函数:

           ·一个进程希望复制自身,从而使得父子进程能同时执行不同段的代码,通常来说这种应用会涉及网络服务:父进程等待远端的一个请求或应答,当收到这个请求或者应答的时候调用fork创建一个子进程来完成处理,而自己继续等待远端的请求或应答。

           ·进程想执行另外一个程序,例如在shell中调用用户所生成的应用程序。

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

           fork函数没有参数,它被调用一次,但是返回两次:

           ·对于父进程而言:函数的返回值是子进程的进程标识符(PID)。

           ·对于子进程而言:函数的返回值是0。一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得父进程的标识符,所以不需要在这里返回父进程的进程标识符。

           ·如果出错,返回值为“-1”。

    数据空间共享

           当fork函数返回后,子进程和父进程都从调用fork函数的下一条语句开始执行,但是父进程或子进程哪个先执行是随机的,这个取决于具体的调度算法。

           通常来说,fork所创建的子进程将会从父进程中拷贝父进程的数据空间、堆空间和栈空间,并且和父进程一起共享正文段,需要注意的是子进程所拷贝的仅仅是一个副本,和父进程的相应部分是完全独立的。

    vfork

           在使用fork函数创建一个进程后,可以不使用exec系列函数来执行新的程序,如果要执行新的程序则必须调用exec系列函数。在这种情况下可以使用vfork函数,该函数在创建完一个新的进程后自动实现exec系列函数的功能。

    #include <sys/types.h>
    #include <unistd.h>
    
    pid_t vfork(void); //子进程为0,父进程不为0

           fork与vfork之间的区别如下:

           ·fork要拷贝父进程的数据段,而vfork不需要完全拷贝父进程的数据段,在子进程没有调用exec系列函数或exit函数之前,子进程与父进程共享数据段。

           ·vfork函数会自动调用exec系列函数去执行另一个程序。

           ·fork不对父子进程的执行次序进行任何限制,而在vfork中,子进程先运行,父进程挂起,直到子进程调用了exec系列函数或exit之后,父子进程的执行次序才不再有任何限制。

    执行进程

           在Linux中可以调用fork函数来创建一个子进程,该子进程几乎复制了父进程的全部内容,但是如果需要在子进程中执行一些自定义动作,则需要调用exec函数族。

           当调用exec系列函数的时候,该进程执行的程序被立即替换为新的程序,而新程序则从main函数开始执行,并用它来取代原调用进程的正文段、数据段、堆和栈,但其进程标识符和进程描述符是不会改变的。

           在Linux中通常会在如下两种情况下调用exec函数族:

           ·当进程不能再为系统和用户做出任何贡献时,就可以调用exec函数族让自己“重生”。

           ·如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用exec函数族中的一个函数,这样看起来就像通过执行应用程序而产生了一个正文段、数据段等都与其父进程不同的全新进程。

    #include <unistd.h>
    
    int execl(const char *path, const char *arg, ...);
    int execv(const char *path, char *const argv[]);
    int execle(const char *path, const char *arg, ..., char *const envp[]);
    int execlp(const char *file, const char *arg, ...);
    int execvp(const char *file, char *const argv[]);
    int execvpe(const char *file, char *const argv[], char *const envp[]);

           上述exec函数族的参数说明如下:

           ·path:可执行目标文件的路径名

           ·file:可执行目标文件的文件名

           ·arg:目标文件的路径名

           ·argv:一个字符指针数组,由它指出该目标程序使用的命令行参数表,按照约定第一个字符指针指向与path或file相同的字符串,最后一个指针指向一个空字符串,其余的指向该程序执行时所带的命令行参数。

           ·envp:与argv一样也是一个字符指针数组,由它指出该目标程序执行时的进程环境,它也以一个空字符串结束。

           execl、execle、execlp这三个函数用于表示命令行参数的一般方式是:

    char *arg0, char *arg1, ..., char *argn, (char *)0

     

    退出进程

           当一个进程执行完成后必须要退出,退出时内核会进行一系列操作,包括释放缓冲区等。通常来说Linux的应用程序代码会调用exit系列函数来退出一个进程。

    #include <stdlib.h>
    #include <unistd.h>
    
    void exit(int status);
    void _exit(int status);
    void _Exit(int status);

           exit系列函数没有返回值,其使用一个称为终止状态(exit status)的整形变量作为参数,Linux内核会对这个终止状态进行检查,当异常终止时,Linux内核会直接产生一个终止状态字,描述异常终止的原因,可以通过wait或者waitpid函数来获得终止状态字。父进程也可以通过检查终止状态来获得子进程的状态。如果main函数的返回值定义为整型并且main函数正常执行到最后一条语句返回,则终止状态是0。

           _exit与exit的区别:

           _exit:直接使进程停止运行,清除其占用的内存空间,并清除其在内核中的各种数据结构。

           exit:在_exit的基础上做了一些包装,在执行退出之前加了若干道程序。如调用前要检查文件的打开情况,把文件缓冲区中的内容写回文件(清理I/O缓冲)。

           当一个进程退出时,可能存在以下两种状态:

           ·其父进程恰好因忙于其他事务暂时不能接收子进程的终止状态,如果此时子进程完全消失,那么当父进程处理完其他事务想检查子进程的情况时就没有可用的信息了。所以Linux内核为每个已退出的进程保留一定的信息,一般至少包含进程标识符、终止状态字、进程处理器时间等信息。父进程可以通过调用wait或waitpid得到相应的信息,在此之后,Linux内核再将这些数据释放。通常把这种已经结束,但其父进程尚未检查其终止状态的进程称为僵尸进程

           ·如果父进程可能先于子进程结束,此时init进程就会自动成为该子进程的父进程。

           由以上可知,当调用exit系列函数或者return函数返回时,其实进程并没有真正的完全消失,其还在继续占用部分资源。如果这种僵尸进程过多,就会大大影响系统的性能。

    销毁进程

           当一个进程使用exit系列函数退出时,会在内存中保留部分数据以供父进程查询,同时也会产生一个终止状态字,然后Linux内核会发出一个SIGCHLD信号以通知父进程。因为子进程的结束对于父进程是异步的,因此这个SIGCHLD信号对于父进程也是异步的,父进程可以不响应。

           父进程对于退出后的子进程的默认状态是不处理的,这样会导致系统中的僵尸进程浪费了系统资源,此时应该调用wait或waitpid函数对这些僵尸进程进行处理。

    #include <sys/types.h>
    #include <sys/wait.h>
    
    pid_t wait(int *status);
    pid_t waitpid(pid_t pid, int *status, int options);

           在调用wait或waitpid之后可能存在如下三种情况:

           ·如果该父进程的所有子进程都还在运行,则阻塞父进程自身以等待子进程的运行结束。

           ·如果有一个子进程已经结束,则父进程取得该子进程的终止状态,并且立即返回。

           ·如果该父进程没有任何子进程,则立即出错返回。

    wait函数

           如果wait函数调用成功则返回子进程的标识符,如果失败则返回-1,其中参数status是一个整型指针,可以用于存放子进程的终止状态,也可以定义为一个空指针。

           wiat函数与waitpid函数不同,在有一个子进程终止之前,wait函数让父进程阻塞以等待子进程退出,而waitpid有一个参数可以让父进程不阻塞。并且在一个父进程有多个子进程的情况下,如果其中有一个子进程退出则会返回该子进程的进程标识符。

    wait函数返回的宏
    说明
    WIFEXITED(status) 当子进程正常结束时返回为真
    WIFSIGNALED((status) 当子进程异常结束时返回为真
    WEXITSTATUS(status) 当WIFEXITED(status)为真时调用,返回状态字的低8位
    WTERMSIG(status) 当WIFSIGNALED(status)为真时调用,返回引起状态终止的信号代号

    waitpid函数

           在使用wait函数时,如果父进程的任何一个子进程返回则wait函数返回,而waitpid函数可以通过参数来指定需要等待的子进程。waitpid函数的参数pid用于对子进程进行相应的筛选:

           ·pid>0:只等待PID为pid的子进程,不管其他已经有多少子进程结束退出了,只要指定的子进程还没有结束,waitpid就一直等待下去。

           ·pid=-1:等待任何一个子进程退出,没有任何限制,此时waitpid等价于wait。

           ·pid<-1:等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

           waitpid函数的参数options用于进一步控制waitpid函数的操作,其可以是0,也可以是WNOHANG和WUNTRACED两个选项之一,或者是使用“|”符号连接的“或”操作。对这两个选项的定义如下:

           ·WNOHANG:如果由pid指定的子进程并不是立即可用的,则waitpid函数不阻塞,此时返回“0”。

           ·WUNTRACED:如果某实现支持作业控制,而由pid指定的任意子进程已经处于暂停状态,并且未报告过,则返回其状态。

           总体而言,waitpid函数提供了wait函数所没有的三个功能:

           ·能够等待一个指定的进程结束。

           ·能够不阻塞父进程获得子进程的状态。

           ·支持作业控制。

    展开全文
  • 通过编写多进程程序,熟练掌握fork()、exec()、wait()和waitpid()等函数的使用,进一步理解在Linux多进程编程的步骤。 实验内容 该实验有3个进程,其中一个为父进程,其余两个是该父进程创建的子进程,其中一...

    实验目的

    通过编写多进程程序,熟练掌握fork()、exec()、wait()和waitpid()等函数的使用,进一步理解在Linux中多进程编程的步骤。

    实验内容

    该实验有3个进程,其中一个为父进程,其余两个是该父进程创建的子进程,其中一个子进程运行“ls -l”指令,另一个子进程在暂停5s后异常退出。父进程先用阻塞方式等待第一个进程的结束,然后用非阻塞方式等待另一个子进程的退出,待收集到第2个子进程结束的消息后,父进程就返回。

    实验步骤

    该实验的流程图如下

    实验源代码

    先看一下下面的代码,这个程序能得到我们所希望的结果吗?它的运行会产生几个进程?

    执行结果如下图

    也有可能会出现下面的结果

    分析执行结果可以指知道,这里其实是产生了3个子进程,在子进程2中又产生了一个子进程1。

    下面咱们贴出正确的实验代码:

    执行结果如下图

    不论程序执行多少次,结果都是一样的。对比上一个结果,咱们就可以理解多进程该怎么创建了。一定要明确创建子进程的父进程是哪一个。

    相关实验代码上传到网站,multi_proc_wrong.c下载,multi_proc.c下载

    免费下载地址在 http://linux.linuxidc.com/

    用户名与密码都是www.linuxidc.com

    具体下载目录在 /2013年资料/6月/12日/Linux多任务编程

    展开全文
  •  通过编写多进程程序,熟练掌握fork()、exec()、wait()和waitpid()等函数的使用,进一步理解在Linux多进程编程的步骤。 实验内容  该实验有3个进程,其中一个为父进程,其余两个是该父进程创建的子进程,其中一...

    实验目的

       通过编写多进程程序,熟练掌握fork()、exec()、wait()和waitpid()等函数的使用,进一步理解在Linux中多进程编程的步骤。

    实验内容

       该实验有3个进程,其中一个为父进程,其余两个是该父进程创建的子进程,其中一个子进程运行“ls -l”指令,另一个子进程在暂停5s后异常退出。父进程先用阻塞方式等待第一个进程的结束,然后用非阻塞方式等待另一个子进程的退出,待收集到第2个子进程结束的消息后,父进程就返回。

    实验步骤

       该实验的流程图如下

       

    实验源代码

       先看一下下面的代码,这个程序能得到我们所希望的结果吗?它的运行会产生几个进程?

                                                                                                                                                                                     

       执行结果如下图

         

      也有可能会出现下面的结果

      

      分析执行结果可以指知道,这里其实是产生了3个子进程,在子进程2中又产生了一个子进程1。

      下面咱们贴出正确的实验代码:

                                                                                                                                           

      执行结果如下图

       

      不论程序执行多少次,结果都是一样的。对比上一个结果,咱们就可以理解多进程该怎么创建了。一定要明确创建子进程的父进程是哪一个。

      相关实验代码上传到网站,multi_proc_wrong.c点此下载,multi_proc.c点此下载

    /************************************************************************************************

    *欢迎关注本人公众号:BigBearIT,一起分享交流知识,更多精彩等着你!

    *************************************************************************************************/


    展开全文
  • 最近在学习linux环境高级编程,多进程编程算是编程中的最重要的一个部分了,本文让我学习和明白了很多,所以转载过来。让更多想多线程编程的人学习。只有顶到首页才能让更多的人学习。 文章摘要:  多线程程序...
      最近在学习linux环境高级编程,多进程编程算是编程中的最重要的一个部分了,本文让我学习和明白了很多,所以转载过来。让更多想多线程编程的人学习。只有顶到首页才能让更多的人学习。
    

    文章摘要:
       多线程程序设计的概念早在六十年代就被提出,但直到八十年代中期,Unix系统中才引入多线程机制,如今,由于自身的许多优点,多线程编程已经得到了广泛的应用。本文我们将介绍在Linux下编写多进程和多线程程序的一些初步知识。


    --------------------------------------------------------------------------------

    正文:
    Linux下的多进程编程初步

    1 引言
       对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一:它执行一次却返回两个值。fork函数是Unix系统最杰出的成就之一,它是七十年代UNIX早期的开发者经过长期在理论和实践上的艰苦探索后取得的成果,一方面,它使操作系统在进程管理上付出了最小的代价,另一方面,又为程序员提供了一个简洁明了的多进程方法。与DOS和早期的Windows不同,Unix/Linux系统是真正实现多任务操作的系统,可以说,不使用多进程编程,就不能算是真正的Linux环境下编程。
       多线程程序设计的概念早在六十年代就被提出,但直到八十年代中期,Unix系统中才引入多线程机制,如今,由于自身的许多优点,多线程编程已经得到了广泛的应用。
       下面,我们将介绍在Linux下编写多进程和多线程程序的一些初步知识。

    2 多进程编程
       什么是一个进程?进程这个概念是针对系统而不是针对用户的,对用户来说,他面对的概念是程序。当用户敲入命令执行一个程序的时候,对系统而言,它将启动一个进程。但和程序不同的是,在这个进程中,系统可能需要再启动一个或多个进程来完成独立的多个任务。多进程编程的主要内容包括进程控制和进程间通信,在了解这些之前,我们先要简单知道进程的结构。

      2.1 Linux下进程的结构
       Linux下一个进程在内存里有三部分的数据,就是"代码段"、"堆栈段"和"数据段"。其实学过汇编语言的人一定知道,一般的CPU都有上述三种段寄存器,以方便操作系统的运行。这三个部分也是构成一个完整的执行序列的必要的部分。
       "代码段",顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。"堆栈段"存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。这其中有许多细节问题,这里限于篇幅就不多介绍了。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。

      2.2 Linux下的进程控制
       在传统的Unix环境下,有两个基本的操作用于创建和修改进程:函数fork( )用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;函数族exec( )用来启动另外的进程以取代当前运行的进程。Linux的进程控制和传统的Unix进程控制基本一致,只在一些细节的地方有些区别,例如在Linux系统中调用vfork和fork完全相同,而在有些版本的Unix系统中,vfork调用有不同的功能。由于这些差别几乎不影响我们大多数的编程,在这里我们不予考虑。
       2.2.1 fork( )
       fork在英文中是"分叉"的意思。为什么取这个名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就"分叉"了,所以这个名字取得很形象。下面就看看如何具体使用fork,这段程序演示了使用fork的基本框架:

    1. void main(){   
    2. int i;   
    3. if ( fork() == 0 ) {   
    4. /* 子进程程序 */   
    5. for ( i = 1; i <1000; i ++ ) printf("This is child process/n");   
    6. }   
    7. else {   
    8. /* 父进程程序*/   
    9. for ( i = 1; i <1000; i ++ ) printf("This is process process/n");   
    10. }   
    11. }   
    void main(){ 
    int i; 
    if ( fork() == 0 ) { 
    /* 子进程程序 */ 
    for ( i = 1; i <1000; i ++ ) printf("This is child process/n"); 
    } 
    else { 
    /* 父进程程序*/ 
    for ( i = 1; i <1000; i ++ ) printf("This is process process/n"); 
    } 
    } 
    


       程序运行后,你就能看到屏幕上交替出现子进程与父进程各打印出的一千条信息了。如果程序还在运行中,你用ps命令就能看到系统中有两个它在运行了。
       那么调用这个fork函数时发生了什么呢?fork函数启动一个新的进程,前面我们说过,这个进程几乎是当前进程的一个拷贝:子进程和父进程使用相同的代码段;子进程复制父进程的堆栈段和数据段。这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。它们再要交互信息时,只有通过进程间通信来实现,这将是我们下面的内容。既然它们如此相象,系统如何来区分它们呢?这是由函数的返回值来决定的。对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零。在操作系统中,我们用ps函数就可以看到不同的进程号,对父进程而言,它的进程号是由比它更低层的系统调用赋予的,而对于子进程而言,它的进程号即是fork函数对父进程的返回值。在程序设计中,父进程和子进程都要调用函数fork()下面的代码,而我们就是利用fork()函数对父子进程的不同返回值用if...else...语句来实现让父子进程完成不同的功能,正如我们上面举的例子一样。我们看到,上面例子执行时两条信息是交互无规则的打印出来的,这是父子进程独立执行的结果,虽然我们的代码似乎和串行的代码没有什么区别。
       读者也许会问,如果一个大程序在运行中,它的数据段和堆栈都很大,一次fork就要复制一次,那么fork的系统开销不是很大吗?其实UNIX自有其解决的办法,大家知道,一般CPU都是以"页"为单位来分配内存空间的,每一个页都是实际物理内存的一个映像,象INTEL的CPU,其一页在通常情况下是4086字节大小,而无论是数据段还是堆栈段都是由许多"页"构成的,fork函数复制这两个段,只是"逻辑"上的,并非"物理"上的,也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别的"页"从物理上也分开。系统在空间上的开销就可以达到最小。
       下面演示一个足以"搞死"Linux的小程序,其源代码非常简单:
      

    1. void main()   
    2.    {   
    3.      for( ; ; ) fork();   
    4.    }   
    void main() 
       { 
         for( ; ; ) fork(); 
       } 
    


       这个程序什么也不做,就是死循环地fork,其结果是程序不断产生进程,而这些进程又不断产生新的进程,很快,系统的进程就满了,系统就被这么多不断产生的进程"撑死了"。当然只要系统管理员预先给每个用户设置可运行的最大进程数,这个恶意的程序就完成不了企图了。
       2.2.2 exec( )函数族
       下面我们来看看一个进程如何来启动另一个程序的执行。在Linux中要使用exec函数族。系统调用execve()对当前进程进行替换,替换者为一个指定的程序,其参数包括文件名(filename)、参数列表(argv)以及环境变量(envp)。exec函数族当然不止一个,但它们大致相同,在Linux中,它们分别是:execl,execlp,execle,execv,execve和execvp,下面我只以execlp为例,其它函数究竟与execlp有何区别,请通过manexec命令来了解它们的具体情况。
       一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。(不过exec类函数中有的还允许继承环境变量之类的信息。)
       那么如果我的程序想启动另一程序的执行但自己仍想继续运行的话,怎么办呢?那就是结合fork与exec的使用。下面一段代码显示如何启动运行其它程序:

    1. char command[256];   
    2. void main()   
    3. {   
    4. int rtn; /*子进程的返回数值*/   
    5. while(1) {   
    6. /* 从终端读取要执行的命令 */   
    7. printf( ">" );   
    8. fgets( command, 256, stdin );   
    9. command[strlen(command)-1] = 0;   
    10. if ( fork() == 0 ) {   
    11. /* 子进程执行此命令 */   
    12. execlp( command, command );   
    13. /* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/   
    14. perror( command );   
    15. exit( errorno );   
    16. }   
    17. else {   
    18. /* 父进程, 等待子进程结束,并打印子进程的返回值 */   
    19. wait ( &rtn );   
    20. printf( " child process return %d/n",. rtn );   
    21. }   
    22. }   
    23. }   
    char command[256]; 
    void main() 
    { 
    int rtn; /*子进程的返回数值*/ 
    while(1) { 
    /* 从终端读取要执行的命令 */ 
    printf( ">" ); 
    fgets( command, 256, stdin ); 
    command[strlen(command)-1] = 0; 
    if ( fork() == 0 ) { 
    /* 子进程执行此命令 */ 
    execlp( command, command ); 
    /* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/ 
    perror( command ); 
    exit( errorno ); 
    } 
    else { 
    /* 父进程, 等待子进程结束,并打印子进程的返回值 */ 
    wait ( &rtn ); 
    printf( " child process return %d/n",. rtn ); 
    } 
    } 
    } 


     

       此程序从终端读入命令并执行之,执行完成后,父进程继续等待从终端读入命令。熟悉DOS和WINDOWS系统调用的朋友一定知道DOS/WINDOWS也有exec类函数,其使用方法是类似的,但DOS/WINDOWS还有spawn类函数,因为DOS是单任务的系统,它只能将"父进程"驻留在机器内再执行"子进程",这就是spawn类的函数。WIN32已经是多任务的系统了,但还保留了spawn类函数,WIN32中实现spawn函数的方法同前述UNIX中的方法差不多,开设子进程后父进程等待子进程结束后才继续运行。UNIX在其一开始就是多任务的系统,所以从核心角度上讲不需要spawn类函数。
       在这一节里,我们还要讲讲system()和popen()函数。system()函数先调用fork(),然后再调用exec()来执行用户的登录shell,通过它来查找可执行文件的命令并分析参数,最后它么使用wait()函数族之一来等待子进程的结束。函数popen()和函数system()相似,不同的是它调用pipe()函数创建一个管道,通过它来完成程序的标准输入和标准输出。这两个函数是为那些不太勤快的程序员设计的,在效率和安全方面都有相当的缺陷,在可能的情况下,应该尽量避免。

    展开全文
  • 谈谈dpdk应用层包处理程序多进程和多线程模型选择时的若干考虑 看到知乎上有个关于linux多进程、多线程的讨论:链接地址 自己项目里也对这个问题有过很多探讨和测试,所以正好开贴整理一下,题目有点长,其实就...
  • 导入包from multiprocessing import Pool, cpu_count def split(tasks, core,list_count): """ 切分任务 """... each_part = math.ceil(list_count / core) + 1 ...
  • linux 多进程编程详解

    2018-08-29 20:24:46
    1.创建进程fork() 1.1头文件 #include&lt;unistd.h&gt; #include&lt;sys/types.h&gt; 1.2函数原型 pid_t fork( void); pid_t 是一个宏定义,其实质是int 被定义在#include 1.3 返回值...
  • 最近学习Linux,看到“hairetz的专栏”的帖子不错,特转来大家一起学习。 很想写点关于多进程和多线程的东西,我确实很爱他们。但是每每想动手写点关于他们的东西,却总是求全心理作祟,始终动不了手。 今天终于下了...
  • Linux多进程编程(C语言) 一、 进程简介 1、进程是程序的执行。程序是静态的,进程是动态的。 2、进程在内存中有三部分组成:数据段、堆栈段和代码段。  代码段:就是存放程序代码的数据,如果有数个进程...
  • 关键字: linux进程、杀进程、起进程 1.查进程  ps命令查找与进程相关的PID号:  ps a 显示现行终端机下的所有程序,包括其他用户的程序。  ps -A 显示所有程序。  ps c 列出程序时,显示每个程序真正的...
  • 鱼还是熊掌:浅谈多进程多线程的选择 关于多进程和多线程,教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小...”、“Linux下用多进程还是多线程?”等等期望一劳永逸的问题,我只能说:没有
  • 说到进程,首先要明确的一个概念就是什么是进程进程是“a program in execution”。一个进程由如下元素组成: –程序的上下文(context),它是程序当前执行的状态 –程序的当前执行目录 –程序访问的文件和目录 ...
  • fork()函数是Linux系统中唯一可以创建一个新进程的方法。其新创建的进程称为子进程,原进程称为父进程。子进程将从父进程处继承了整个父进程的地址空间(包括进程上下文、代码段、进程堆栈、内存信息、打开的文件...
  • Linux的多任务多进程

    2016-07-05 20:02:58
    任务并不是一起执行,只是因为所有程序都在获取执行权,CPU处理速度很快,看上去是在进行任务 二、进程 (一)、进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元 相关术语: 1、...
  • Linux多进程编程

    2017-11-18 23:12:50
    什么是一个进程?进程这个概念是针对系统而不是针对用户的,对用户来说,他面对的概念是程序。当用户敲入命令执行一个程序的时候,对系统而言,它将启动一个进程。...多进程编程的主要内容包括进程控制和进程间通信
  • Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建,新创建的进程叫做子进程,而创建子进程进程叫做父进程。那个在系统启动及完成初始化之后,Linux自动创建的进程...
  • 一、进程 进程是一个程序的动态运行实例一个正在执行的程序能分配处理器并由处理器执行的实体内核观点:担当分配系统资源(CPU时间,内存)的实体。...进程程序的一次动态运行实例,程序是可以被保存的,而进程
  •  通过编写多进程程序,熟练掌握fork()、exec()、wait()和waitpid()等函数的使用,进一步理解在Linux多进程编程的步骤。 实验内容  该实验有3个进程,其中一个为父进程,其余两个是该父进程创建的子进程,...
  • 多进程程序的特点  进程是一个具有独立功能的程序关于某个数据集合的一次可以并发执行的运行活动,是处 于活动状态的计算机程序。  进程作为构成系统的基本细胞, 不仅是系统内部独立运行的实体, 而且是...
1 2 3 4 5 ... 20
收藏数 452,133
精华内容 180,853
关键字:

多进程linux程序