c语言中的多进程的实现

c语言中的多进程的实现,第1张


一、进程的相关概念 1.什么是进程?

进程:程序的一次执行过程就会创建一个进程,进程就是一个正在执行的任务,进程的所用信息通过task_struct(PCB)结构体记录,进程是分配资源的最小单位,进程是调度器调度的实体,每一个进程单独拥有自己的0-3G的(虚拟内存)空间,这0-3G包含堆、栈区、静态库。


还包括文件描述符的表。


程序执行结束的时候0-3G的空间就会被释放了,每一个进程都有自己的唯一的标识pid,pid就是进程号。


进程号pid:进程的唯一的标识,它是一个非负整数,由 *** 作系统随机分配。


pid就可以理解为进程的身份z号ID。


系统中正在执行的进程的进程号可以在/proc目录下查看。


注:在 *** 作系统上查看一个 *** 作系统能够分配的最大的进程号的命令cat /proc/sys/kernel/pid_max                                                                                                              ===>131072

2.进程和程序有什么区别?

进程:进程是动态的,具有生命周期,正在运行的进程在内存上存放。


程序:程序是静态的文件,没有生命周期,它是在磁盘上存放着。


在执行一个程序的时候就会创建一个进程。


3.进程的组成部分

进程的组成部分:进程控制块PCB,数据段,程序段组成

进程控制块(pcb):进程控制块其实在内核空间就是一个task_struct结构体,这个结构体记录了进程的所有的信息,例如进程的标号pid,进程的执行状态,进程的使用的内存空间等等。


数据段:堆、栈、.data、.bss

程序段:存放的是a.out的可执行程序的文本

示例:

struct task_struct{
    进程的pid
    进程的状态state
    进程运行的内存
    ...
};
4.进程的种类

交互进程:交互进程是由shell控制的,交互进程可以是前台进程,也可以是后台进程(./a.out &)。


例如文本编辑器

批处理进程:批处理进程不属于某一个终端,在 *** 作系统内部维护了一个队列。


例如gcc编译程序的过程

守护进程:守护进程一直在后台执行,随着系统的启动而运行,随着系统的终止而终止。


例如:服务

5.一些特殊的进程

0号进程(idle):在linux系统启动的时候,执行 *** 作系统代码的进程,当没有其他进程需要执行的时候就会运行0号进程

1号进程(init):1号进程是0号进程通过kernel_thread(内核)函数创建的,后面的进程都是拷贝这个进程得到的。


1号进程可以回收没有回收的资源

2号进程(kthreadd):2号进程是0号进程通过kernel_thread(内核)函数创建的,调度进程执行的进程

6.进程的状态

进程的状态
D    //不可中断的等待态(sleep)(不可被信号中断)
R    //运行态
S    //可中断的等待态(sleep)(可被信号中断)
T    //停止态
t    //代码追踪调试状态
X    //死亡态
Z    //僵尸,子进程死了,父进程没有为他收尸,如果父进程也死掉,init进程为它收尸

进程的附加状态
< 高优先级的进程
N 低优先级的进程
L 将进程的所在的内存所在内存区间
s 会话组组长
l 在进程中包含多线程
+ 前台进程


二、进程 *** 作相关的一些命令 1.ps命令

 ①ps -ef (查看进程的pid和ppid)

运行结果:

UID PID PPID C STIME TTY  TIME    CMD
root   1      0     0  05:11    ?   00:00:06 /sbin/init auto noprompt
root   2      0     0  05:11    ?   00:00:00 [kthreadd]
root   3      2     0  05:11    ?   00:00:00 [rcu_gp]
root   4      2     0  05:11    ?   00:00:00 [rcu_par_gp]

//UID:进程所属的用户
//PID:进程的ID号
//PPID:父进程的ID号
//pidof a.out 可以查看a.out的进程号

②ps aux (查看进程的状态)

运行结果:
USER PID %CPU %MEM VSZ     RSS TTY STAT START TIME COMMAND
root          1    0.0       0.1 225416  7896   ?     Ss     05:11   0:06 /sbin/init auto noprompt
root          2    0.0       0.0      0          0      ?     S       05:11   0:00 [kthreadd]
root          3    0.0       0.0      0          0      ?     I<      05:11   0:00 [rcu_gp]
root          4    0.0       0.0      0          0      ?     I<      05:11   0:00 [rcu_par_gp]
root          6    0.0       0.0      0          0      ?     I<      05:11   0:00 [kworker/0:0H-kb]
root          9    0.0       0.0      0          0      ?     I<      05:11   0:00 [mm_percpu_wq]
root         10   0.0       0.0      0          0      ?     S       05:11   0:00 [ksoftirqd/0]

//PID:进程号
//%cpu:占用的cpu资源
//%mem:占用内存的资源
//VSZ:虚拟内存的大小
//RSS:实际的物理内存的大小
//TTY:如果没有对应的终端就是?
//STAT:进程的状态

③ps ajx

运行结果:
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
     0      1      1      1    ?       -1        Ss      0   0:06 /sbin/init 
     0      2      0      0    ?       -1        S        0   0:00 [kthreadd]
     2      3      0      0    ?       -1        I<       0   0:00 [rcu_gp]
     2      4      0      0    ?       -1        I<       0   0:00 [rcu_par_gp]
     2      6      0      0    ?       -1        I<       0   0:00 [kworker/0:0H-kb]
     2      9      0      0    ?       -1        I<       0   0:00 [mm_percpu_wq]
     2     10      0     0    ?       -1        S        0   0:00 [ksoftirqd/0]
     2     11      0     0    ?       -1        I          0   0:20 [rcu_sched]
     2     12      0     0    ?       -1        S        0   0:00 [migration/0]
     2     13      0     0    ?       -1        S        0   0:00 [idle_inject/0]

//SID:会话:会话中有很多进程组,1前台进程组 ,多个后台进程组
//PGID:进程组:前台进程组 ,后台进程组
//PID:进程号
//PPID:父进程号
//TPGID:-1守护进程
注:通过top命令动态查看cpu和mem的使用率,htop(sudo apt-get install htop)更好看一点

2.kill命令

信号:在linux系统中软件模拟的类似中断的一种机制。


当给一个进程发送信号的时候进程收到信号会执行这个信号的动作(默认,忽略,捕捉)

kill -l 查看信号
kill -信号号 进程的pid
 如:kill -2 pid
        kill -9 pid
示例:
pidof a.out   //查看a.out的进程号
kill -9  10435 //杀死a.out的进程
killall a.out  //将a.out运行的所有的进程全部杀掉

3.切换进程状态的命令

前台运行状态:​ ./a.out //前台运行程序(状态为R+)

后台运行状态:​ ./a.out & //后台运行程序(状态为R)

​ ps aux | grep a.out //查看进程运行的状态

进程前后台的切换:

​ ./a.out //前台正在执行的进程

​ ctrl+z //让前台运行的程序先进入停止状态(状态为T)

kill -18 (a.out的pid)  或  bg 作业号 //后台运行进程

fg (作业号) //让后台运行的进程变为前台运行的进程,如果fg后面没有跟作业号,默认将倒数第一个后台进程变为前台进程

​ jobs -l //查看后台运行的所有的进程

4.特殊状态的进程

僵尸进程:子进程结束之后,父进程没有为它收尸,此时子进程就是僵尸进程,只有当父进程结束收,被init收尸

孤儿进程:父进程先于子进程结束,子进程就是孤儿进程,孤儿进程被init进程收养。



三、多进程的C语言程序实现 1.进程是怎么创建得到的

在创建进程的时候得到子进程,子进程就是拷贝父进程得到的。


拷贝父进程的内容后pid是 *** 作系统随机分配的,会记录父进程的pid为ppid。


2.进程创建的函数接口

pid_t fork(void);
功能:创建子进程

#include 
#include 
参数:
        @无
返回值:
        >0 子进程的pid返回给了父进程
        =0 子进程
        -1 失败置位错误码

pid_t getpid(void);
功能:获取当前进程的pid
pid_t getppid(void);
功能:获取父进程的pid

示例:

#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    pid_t pid;
  
    pid = fork();
    if(pid == -1){
        perror("fork error");
        return -1;
    }else if(pid == 0){
        //子进程的代码区
        printf("我是子进程...pid = %d,ppid = %d\n",getpid(),getppid());
    }else if(pid > 0){
        //父进程的代码区
        printf("我是父进程...cpid = %d,pid = %d,ppid = %d\n",pid,getpid(),getppid());
        while(1);
    }
    //父子进程执行没有先后顺序,子进程如果执行结束了,父进程没有为他收尸,子进程变成僵尸进程
    //将父进程结束的时候,init进程为们收尸
    return 0;
}

注:父子进程执行顺序及时机:时间片轮询,上下文切换

3.进程结束方式(return/exit/_exit)

return :return的作用:用来退出一个函数执行的栈空间,如果把return放在main函数可以具备退出进程的效果,但是如果把return放在子函数return就不能结束一个进程了。


exit:库函数

void exit(int status);
功能:退出一个进程,刷新缓冲区

#include 
参数:
        @status: 成功返回0 ,失败返回负数 
                EXIT_FAILURE  1
                EXIT_SUCCESS  0
返回值:无

_exit:系统调用

void _exit(int status);
功能:退出一个进程,不刷新缓冲区

#include 
参数:
        @status: 成功返回0 ,失败返回负数 
                EXIT_FAILURE  1
                EXIT_SUCCESS  0
返回值:无

示例:

#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    pid_t pid;

    pid = fork();
    if (pid == -1)
    {
        perror("fork error");
        return -1;
    }
    else if (pid == 0)
    {
        //子进程的代码区
        printf("我是子进程pid = %d,ppid = %d",getpid(),getppid());
        //exit(0);  //程序在这里执行结束,并刷新缓冲区
        _exit(0);   //程序在这里执行结束,不会刷新缓冲区
        while(1);
    }
    else if (pid > 0)
    {
        //父进程的代码区
        printf("我是父进程cpid = %d,pid = %d,ppid = %d\n",pid,getpid(),getppid());
        while (1)
            ;
    }

    return 0;
}
4.进程资源回收(wait/waitpid)

pid_t wait(int *wstatus);
功能:功能在父进程为子进程进程收尸处理

#include 
#include 
参数:
        @wstatus:接受exit退出的时候给的值
           NULL,不关注子进程接受的时候返回的值 ===>wait(NULL); 阻塞等待最先结束的子进程结束
返回值:成功返回收尸的pid,失败返回-1置位错误码


pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:为子进程收尸

#include 
#include 
参数:
        @pid:>0 :指定进程号收尸
                  -1 :为任意的子进程收尸
                 =0 :回收同组的子进程
                <-1 : 如pid=-1234,对-1234取绝对值,回收1234这个组的进程的尸体    
        @wstatus:接受exit退出的时候给的值
                NULL,不关注子进程接受的时候返回的值 
        @options: 0:阻塞
                WNOHANG:非阻塞
返回值:成功返回收尸的pid,失败返回-1置位错误码           
            
注:waitpid(-1,NULL,0) = wait(NULL);

示例:

#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    pid_t pid;

    pid = fork();
    if (pid == -1)
    {
        perror("fork error");
        return -1;
    }
    else if (pid == 0)
    {
        //子进程的代码区
        printf("我是子进程pid = %d,ppid = %d\n",getpid(),getppid());
        sleep(5);
        exit(3); //0x14
    }
    else if (pid > 0)
    {
        int status;
        //父进程的代码区
        printf("我是父进程cpid = %d,pid = %d,ppid = %d\n",pid,getpid(),getppid());
       
        //wait(&status);
        waitpid(-1,NULL,0);
        // sleep(7);
        //waitpid(pid,NULL,WNOHANG);
        printf("父进程收尸结束 status = %#x\n",status);
        while (1);
    }

    return 0;
}
5.守护进程的创建

守护进程是后台进程,守护进程相当于系统的服务,随着系统的启动而启动,随着系统的终止而终止。


守护进程不依附终端,如果将终端关闭之后进程依然不能够退出(非守护进程终端结束后进程也就结束了)

需要用到的函数:setsid/chdir/umask/getdtablesize
pid_t setsid(void);
功能:创建一个会话

#include 
#include 
参数:
        @无
返回值:成功返回会话id,失败返回-1置位错误码


int chdir(const char *path);
功能:切换目录

#include 
#include 
参数:
        @path:目录的字符串
返回值:成功返回0,失败返回-1置位错误码
       

mode_t umask(mode_t mask);
功能:修改掩码

#include 
#include 
参数:
        @mask:掩码的值
返回值:总是成功


int getdtablesize(void);
功能:获取进程中能打开的最大的文件描述符的值

#include 
参数:
        @无
返回值:返回最大文件描述符的值,总会成功

守护进程的创建流程图:

示例:

#include 
#include 
#include 
#include 
#include 
int main(int argc, char const *argv[])
{
    pid_t pid;

    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(EXIT_FAILURE);
    }else if(pid == 0){
        //子进程
        int fd,num=0;
        //1.只要父进程退出了,子进程就变成孤儿进程,被init收养
        //2.设置setsid
        if(setsid()==-1)
            PRINT_ERR("setsid error");
        //3.切换目录
        if(chdir("/")==-1)
            PRINT_ERR("chdir error");
        //4.修改掩码
        umask(0);  //mode &(~umask)
        //5.关闭不相关的文件描述符
       for(int i=3;i
6.exec函数族

在使用fork创建多进程的时候,子进程只能够执行子进程代码块里面的程序,有人就设想能不能让子进程执行任意想执行的代码,答案是确定,通过exec函数族实现。


①execl/execv函数的使用(程序替换)
int execl(const char *path, const char *arg, .../* (char *) NULL */);
//int execv(const char *path, char *const argv[]);
//execv和execl的用法是一样的,只不过将execl的参数列表放入到argv的指针数组中即可
//char *const argv[] = {
// "a.out","参数1","参数2",... NULL,
//}
功能:执行在参数中给定的可执行程序

#include 
参数:
        @path:新的可执行程序的路径和可执行程序名字  "/home/linux/a.out"
        @arg:可执行程序的参数 "a.out","参数1","参数2"...,NULL
返回值:失败返回-1置位错误码 

示例:

#include 
#include 
#include 
#include 
#include 
#include 

char *const argvv[] = {
    "ls","-l","/home/linux",NULL
};

int main(int argc, char const *argv[])
{
    pid_t pid;

    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(EXIT_FAILURE);
    }else if(pid == 0){
        if(execv("/bin/ls",argvv)==-1)
        //if(execl("/bin/ls", "ls","-l","/home/linux",NULL)==-1)
            PRINT_ERR("execl error");
        //这里的“11111111111”是不会打印的,因为整个程序都被ls的可执行程序替换掉了
         printf("111111111111111111111111\n");
    }else{
        wait(NULL);
    }

    return 0;
}

②execlp/execvp函数(执行程序的时候可以使用PATH环境变量)

int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
//int execvp(const char *file, char *const argv[]);
功能:在执行替换程序的时候,可执行程序的路径不需要写了 (PATH路径下的程序(echo ${PATH}))

#include 
参数:
        @path:新的可执行程序的路径和可执行程序名字  "a.out"
        @arg:可执行程序的参数 "a.out","参数1","参数2"...,NULL  
返回值:失败返回-1置位错误码 
       
注:使用如下命令给路径:
export PATH=${PATH}:/home/linux/IO/day3/
/home/linux/ .bashrc
/etc/environment

示例:

#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    pid_t pid;

    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(EXIT_FAILURE);
    }else if(pid == 0){
         if(execlp("a.out", "a.out",argv[1],NULL)==-1)
            PRINT_ERR("execlp error");
    }else{
        
        wait(NULL);
    }

    return 0;
}

③execle/execve函数(可以传递环境变量)

int execle(const char *path, const char *arg, ...
           /*, (char *) NULL, char * const envp[] */);
//int execvpe(const char *file, char *const argv[], char *const envp[]);
功能:在执行新的可执行程序的时候,向这个新的可执行程序中传递envp的数组(环境变量)

#include 
参数:
        @path:新的可执行程序的路径和可执行程序名字  "a.out"
        @arg:可执行程序的参数 "a.out","参数1","参数2"...,NULL  
        @envp:环境变量的数据
返回值:失败返回-1置位错误码  

示例:

#include 
#include 
#include 
#include 
#include 
#include 

//在向env中传递环境变量的时候,不是非得传递environ,可以通过如下方式自己定义
char * const envp[] = {
    "AA=aa",
    "/home/linux",
    NULL,
};
extern char **environ;
int main(int argc, char const *argv[])
{
    pid_t pid;

    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(EXIT_FAILURE);
    }else if(pid == 0){
        int i;
      //bash在执行a.out的时候,会将bash中的环境变量传递给a.out可执行程序
      //environ[0] = "PATH=/bin:/sbin/"
      //environ[1] = "HOME=/home/linux"
      //NULL
        for(i=0;environ[i]!=NULL;i++){
           printf("%s\n",environ[i]);
         }
      //execle函数就是在当前子进程中不执行原来的代码了,取而代之的执行env这个可执行程序
      //e:表示可以向env这个可执行程序中传递环境变量,将bash传递给a.out环境变量在传递给env
      //可执行程序。


#if 0 if(execle("./env", "env",NULL,environ)==-1) PRINT_ERR("execle error"); #else char * const argvv[] = { "env",NULL }; if(execve("./env",argvv ,environ)==-1) PRINT_ERR("execle error"); #endif }else{ wait(NULL); } return 0; }

//env.c  =====>gcc env.c -o env
#include 
#include 
#include 

extern char **environ;
int main(int argc, char const *argv[])
{
    printf("-----------------------------------------\n");
    int i;
    //这里拿到的环境变量就是a.out传递过来的
    for(i=0;environ[i]!=NULL;i++){
        printf("%s\n",environ[i]);
    }   
    printf("-----------------------------------------\n");
    // AA=aaa
    //AA就是键,aaa就是值
    //这里的getenv就是在通过键或者值
    printf("PATH = %s\n",getenv("PATH"));
    //执行a.out的可执行程序,会在PATH寻找a.out可执行程序,如果PATH下有a.out就
    //执行程序,否则就执行失败。


if(execlp("a.out","a.out","/home/linux/IO/day3/labixiaoxin.bmp",NULL)==-1){ perror("execlp error"); return -1; } return 0; }

④system函数

int system(const char *command);
功能:fork+execl=system 在一个程序中执行程序

#include 
参数:
        @command:命令  "ls -l"

示例:

#include 
#include 

int main(int argc, char const *argv[])
{
    system("bash shell.sh");
    return 0;
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/562783.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-02
下一篇 2022-04-02

发表评论

登录后才能评论

评论列表(0条)