zoukankan      html  css  js  c++  java
  • Linux多进程编程实例

    前言:编写多进程程序时,我们应该了解一下,创建一个子进程时,操作系统内核是怎样做的。当通过fork函数创建新的子进程时,内核将父进程的用户地址空间的内容复制给子进程,这样父子进程拥有各自独立的用户空间,当父进程修该变量的值时不会影响子进程中的相应变量。但为了提高效率,Linux采用了COW(copy on write)算法,子进程创建时,父子进程享有相同的地址空间,只是在页表中设置cow标识,只有在父进程或子进程执行写数据操作时,才为子进程申请一个物理页,将父进程空间中相应数据所在页的内容复制到该物理页,然后将该页映射到子进程用户地址空间的适当位置。此外,子进程还继承父进程的其他资源,例如父进程打开的文件描述符和工作目录等(因为子进程能继承父进程的文件描述符,所以实现进程之间通信时可以采用管道通信方式)。 子进程和父进程的代码区,以及初始数据是一模一样的,只是fork函数返回的pid在父子进程中有不同的值,所以根据pid的分支语句父子进程会执行不同的结果。如果我们只是想用子进程完成某一部分的功能,当功能完成后我们应该立即使用exit或者return函数结束子进程,不然,若子进程的创建是在一个循环中,并且没有使用exit或者return推出,那么子进程,会像父进程一样执行循环中的代码,然后子进程又创建子进程,无限循环,具体例子额看后面的一个服务器端代码。

     进程创建与退出

    相关api

    pid_t fork()                             //#include <unistd.h> 创建进程

    void exit(int status)      //#include <stdlib.h>  退出进程,退出时会调用atexit注册的函数,先注册的后调用,exit函数还会按需调用fclose函数关闭打开的文件流

    int atexit(void(*func)(void))    //#include <stdlib.h>  为进程注册退出时调用的函数

    void _exit(int status)         //#include <unistd.h>   直接退出进程

    #include <sys/types.h>
    #include <stdio.h>
    #include <stdlib.h>
    int glob=10;
    static void my_exit1(void)  //进程退出时调用函数
    {
        printf("pid=%d first exit handler
    ",getpid());
    }
    
    static void my_exit2(void)
    {
        printf("pid=%d second exit handler
    ",getpid());
    }
    
    int main()
    {
        int local;
        pid_t pid;
        local=8;
        if(atexit(my_exit1)!=0)  //为进程注册的退出时调用函数也会被子进程共享,先注册的后调用
        {
            perror("atexit");
        }
    
        if(atexit(my_exit2)!=0)
        {
            perror("atexit");
        }
    
        if((pid=fork())==0)
        {
            printf("child pid is %d
    ",getpid());   //子进程执行某个任务完后尽量使用exit退出,不然,若父进程中创建的子进程位于循环中,可能会引起未知的行为
        }
        else if(pid>0)
        {
            sleep(10);
            glob++;
            local--;
            printf("father pid is %d
    ",getpid());       
        }
        else
        {
            perror("fork");
        }
        printf("pid=%d,glob=%d,localar=%d
    ",getpid(),glob,local);//这段代码父子进程共享
        return 0;//也可以使用exit(0)
    }

    加载可执行文件映像

    #include <unistd.h>

    int execl(const char *path,const char *arg,...);                     //  l表示命令行参数为以0结束的多个字符串组成 ,v表示命令行参数为以0结束的字符串数组组成

    int execle(const char *path,const char *arg,...,char *const envp[]);  //e表示指定环境表量,原来的环境变量不起作用

    int execlp(const char *file,const char *arg,...);                      //p表示可执行映像文件在环境变量path路径中查找

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

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

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

    path 代表可执行文件路径,arg代表命令行参数

    //testexec.c 被调用程序
    #include <stdio.h> int glob=18; extern char **environ; int main(int argc,char *argv[]) { int local=20; int k; char **ptr=environ; glob++; local++; printf("&glob=%x,&local=%x ",&glob,&local); //打印变量的地址 printf("argc=%d ",argc); for(k=0;k<argc;++k) { printf("argv[%d] %s ",k,argv[k]); //打印命令行参数 } for(ptr=environ;*ptr!=0;++ptr) { printf("%s ",*ptr); //打印环境变量 } return 0; }
    //useexec.c  
    #include <stdio.h> #include <unistd.h> int main() { char *nenv[]={"NAME=value","NEXT=nextvale",(char*)0}; char *nargv[]={"testexec","param1","param2",(char *)0}; //命令行参数都以0结尾 pid_t pid; pid=fork(); switch(pid) { case 0: execve("./testexec",nargv,nenv); //指定环境变量,原来的环境变量不起作用 //execl("./testexec","testexec",0); //不指定环境表量 perror("exec"); exit(1); case -1: perror("fork"); exit(1); default: wait(NULL); printf("exec is completed "); exit(0); } }

    等待子进程结束

    #include <sys/types.h>

    #include <sys/wait.h>

    pid_t wait(int * status)                  //暂停执行,直到一个子进程结束,成功返回进程pid,否则返回-1

    pid_t waitpid(pid_t pid,int *status,int options)     //等待指定子进程结束,options指定等待方式

    返回值:若设置了WNOHANG且未发现子进程则返回0,出错则返回-1

    pid的意义

    pid<-1     等待pid所代表的进程组中的进程

    pid=-1     等待任何子进程

    pid=0      等待与该进程同组的进程

    pid>0      等待的进程标识

    options的意义

    WNOHANG      //表示不阻塞

    WUNTRACED   //当有子进程结束时返回

    //一个回声服务器服务端例子 tcpserver.c
    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <sys/wait.h> #define PORT 4008 #define BACKLOG 10 #define BUFSIZE 4096 //void process(char * cmd); int main(int argc,char* argv[]) { int lsockfd,rsockfd; struct sockaddr_in lsocket,rsocket; if((lsockfd=socket(AF_INET,SOCK_STREAM,0))<0) { perror("socket"); exit(1); } lsocket.sin_family=AF_INET; lsocket.sin_port=htons(PORT); lsocket.sin_addr.s_addr=INADDR_ANY; bzero(&(lsocket.sin_zero),8); if(bind(lsockfd,(struct sockaddr *)&lsocket,sizeof(struct sockaddr))<0) { perror("bind"); exit(1); } if(listen(lsockfd,BACKLOG)<0) { perror("listen"); exit(1); } int sin_size=sizeof(struct sockaddr); int count=0; while(1) { printf("wait for connecting! "); if((rsockfd=accept(lsockfd,(struct sockaddr *)&rsocket,&sin_size))<0) { perror("accept"); continue; } count++; printf("someone connect!,current people %d ",count); if(!fork()) { char str[BUFSIZE]; int numbytes=0; while(1) { if((numbytes=recv(rsockfd,str,BUFSIZE-1,0))<0) { perror("recv"); break; } str[numbytes]=''; if(strcmp(str,"quit")==0) { printf("client quit! "); break; } printf("receive a message: %s ",str); if(send(rsockfd,str,strlen(str),0)<0) { perror("send"); break; } } close(rsockfd); exit(0); } while(waitpid(-1,NULL,WNOHANG)>0) //此处不会阻塞若第三个参数为WUNTRACED则会阻塞 { count--; printf("someone quit!,current people have %d ",count); } } return 0; }
  • 相关阅读:
    python--模块与包
    内置函数 的总结
    迭代器 生成器 列表推导式 生成器表达式的一些总结
    函数的有用信息 带参数的装饰器 多个装饰器装饰一个函数
    函数名的应用(第一对象) 闭包 装饰器
    动态参数 名称空间 作用域 作用域链 加载顺序 函数的嵌套 global nonlocal 等的用法总结
    函数的初识 函数的返回值 参数
    文件操作 常用操作方法 文件的修改
    遍历字典的集中方法 集合的作用 以及增删查的方法
    计算机硬件的小知识
  • 原文地址:https://www.cnblogs.com/CodingUniversal/p/7396671.html
Copyright © 2011-2022 走看看