多进程编程及相关函数

程序是存放在磁盘文件中的可执行文件。程序的执行实例被称为进程,进程具有独立的权限与职责。
每个进程运行在其各自的虚拟地址空间中,进程之间可以通过由内核控制的机制相互通讯。如果系统中某个进程崩溃,它不会影响到其余的进程。
每个Linux进程都一定有一个唯一的数字标识符,称为进程ID,PID(进程ID)总是一个非负整数。
在进程的main函数执行之前内核会启动,编译器在编译的时候会将启动例程编译进可执行文件中。
启动例程的作用:搜集命令行的参数传递给main函数中的argc和argv;搜集环境信息构建环境表并传递给main函数;登记进程的终止函数。

查看系统中的进程

通过下面的命令就可以查看当前系统执行的进程。

ps
ps -ef
ps -aux
ps -ef | more

当前系统执行的进程如下图所示。
在这里插入图片描述
USER是指进程的属主;PID:进程ID号;PPID:父进程ID号;%CPU:进程占用的CPU百分比;%MEM:占用内存的百分比;VSZ:进程虚拟大小;RSS:驻留中页的数量;TTY:终端ID;STAT:进程的状态;START:启动进程的时间;TIME:进程消耗CPU的时间;COMMAND:命令的名称和参数。
进程常见的状态:运行状态®、等待状态(S)、停止状态(T)、僵尸状态(Z)。僵尸状态指的是进程终止或结束,但是在进程表项中仍有记录。


进程标识

获取进程相关标识的函数原型如下。

#include <unistd.h>
#include <sys/types.h>
pid_t getpid(void);   //获取当前进程ID
uid_t getuid(void);   //获得当前进程的实际用户ID
uid_t geteuid(void);   //获得当前进程的有效用户ID
gid_t getgid(void);   //获得当前进程的用户组ID
pid_t getppid(void);   //获得当前进程的父进程ID
pid_t getpgrp(void);   //获得当前进程所在的进程组ID
pid_t getpgid(pid_t pid);  //获得进程ID为pid的进程所在的进程组ID

关于进程标识相关的代码示例如下。

#include <stdio.h>
#include <unistd.h>

int main()
{
   
   
    printf("pid : %d\n",getpid());
    printf("ppid : %d\n",getppid());
    printf("uid : %d\n",getuid());
    printf("euid : %d\n",geteuid());
    printf("user gid : %d\n",getgid());
    printf("gid : %d\n",getpgrp());
    printf("pgid : %d\n",getpgid(getpid()));
    printf("ppgid : %d\n",getpgid(getppid()));
    return 0;
}

程序编译之后的执行结果如下图所示。
在这里插入图片描述


进程创建

进程创建使用的头文件及函数原型如下。

#include <unistd.h>
#include <sys/types.h>
pid_t fork(void); 
pid_t vfork(void);

进程创建函数调用第一,返回两次,在子进程中返回值为0,在父进程中返回值是子进程的进程ID号。
使用进程创建函数fork()创建子进程,父子进程哪个先运行根据系统调度决定,子进程会复制(继承)父进程的内存空间。
使用进程创建函数vfork()创建子进程,子进程先运行且不复制父进程的内存空间。
子进程继承父进程的属性包括:用户信息和权限、目录信息、信号信息、环境、共享存储段、资源限制、堆、栈和数据段,代码段是父子进程共享的
子进程的特有属性:进程ID、锁信息、运行时间、未决信号。
操作文件时的内核结构变化:子进程只继承父进程的文件描述表,不继承但共享文件表项和i-node;父进程创建一个子进程后,文件表项中的引用计数器加1变成2,当父进程作close操作后,计数器减1,子进程还是可以使用文件表项,只有当计数器为0时才会释放文件表项。
使用fork函数创建进程的代码示例如下。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
   
   
    pid_t pid;
    printf("当前运行进程的ID : %d\n",getpid());
    pid = fork();
    if(pid < 0)
        perror("Error!\n");
    else if(pid == 0)
        printf("子进程在运行!fork返回值:%d,当前进程ID:%d,父进程ID:%d\n",pid,getpid(),getppid());
    else
        printf("父进程在运行!fork返回值:%d,当前进程ID:%d,父进程ID:%d\n",pid,getpid(),getppid());
    printf("当前运行进程的ID : %d\n",getpid());
    sleep(1);
    return 0;
}

上面程序编译后运行结果如下图所示。
在这里插入图片描述
可以清楚的看到,在进程创建完毕后,系统中就有了两个进程,一句打印输出的代码被父子进程各执行了一次!
父进程和通过fork()函数创建出的子进程有相同的虚拟空间,但是却有各自的物理空间!
在这里插入图片描述
下面的例子就能够说明这一点。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int global_v = 10;
int main()
{
   
   
    int area_v = 10;
    static int static_v = 10;
    pid_t pid;
    printf("当前运行进程的ID : %d\n",getpid());
    pid = fork();
    if(pid < 0)
        perror("Error!\n");
    else if(pid == 0)
    {
   
   
        global_v = 20;
        area_v = 20;
        static_v = 20;
        printf("子进程中,global_v地址:%p area_v地址:%p static_v地址:%p\n",&global_v,&area_v,&static_v);
        printf("子进程在运行!fork返回值:%d,当前进程ID:%d,父进程ID:%d\n",pid,getpid(),getppid());
    }
    else
    {
   
   
        global_v = 30;
        area_v = 30;
        static_v = 30;
        printf("父进程中,global_v地址:%p area_v地址:%p static_v地址:%p\n",&global_v,&area_v,&static_v);
        printf("父进程在运行!fork返回值:%d,当前进程ID:%d,父进程ID:%d\n",pid,getpid(),getppid());
    }
    printf("当前运行进程的ID : %d, global_v = %d, area_v = %d, static_v = %d\n",getpid(),global_v,area_v,static_v);
    return 0;
}

程序编译后的运行结果如下图所示。
在这里插入图片描述
可以看到,在父子进程中打印的变量地址是一样的(这里打印的变量地址是虚拟地址,并非物理地址),但是里面存放的值却是不一样的,这说明父子进程有相同的虚拟空间,却各自映射了独立的物理空间。
关于虚拟空间和物理空间,下面是一个例子。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西岸贤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值