栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【Linux系统编程】进程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Linux系统编程】进程

目录
  • 一、进程相关概念
  • 二、进程创建函数fork的使用
    • fork函数:
    • getpid函数:
  • 三、进程创建的时候发生了什么事?
  • 四、创建新进程的的应用场景及fork总结
  • 五、vfork函数创建进程
  • 六、进程退出
  • 七、父进程等待子进程退出
    • 孤儿进程(了解,面试):
  • 八、exec族函数
  • 九、system函数
  • 十、popen函数

一、进程相关概念

  关于进程,我们需要掌握五个问题:

  1. 什么是程序,什么是进程,有什么区别?

程序是静态的概念,gcc xxx.c –o pro,磁盘中生成pro文件,叫做程序。进程是程序的一次运行活动,
通俗点意思是程序跑起来了,系统中就多了一个进程。

  1. 如何查看系统中有哪些进程?

a. 使用ps指令查看实际工作中,配合grep来查找程序中是否存在某一个进程
b. 使用top指令查看,类似windows任务管理器

  1. 什么是进程标识符?

每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证。

Pid=0:  称为交换进程(swapper)
作用—进程调度
Pid=1:init进程
作用—系统初始化

编程调用getpid函数获取自身的进程标识符,getppid获取父进程的进程标识符。

  1. 什么叫父进程,什么叫子进程?

进程A创建了进程B,那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系。

  1. C程序的存储空间是如何分配?


a. 正文段也叫代码段,对应上图红色箭头部分,该段主要保存在最低地址的正文部分。
b. 初始化的数据,又称为数据段,对应上图紫色箭头部分,此类变量主要存放在初始化的数据段中。
c. 只定义而未被赋值的变量称为非初始化数据段,也称bss段,此类变量存放在非初始化的数据段中。
d. 通常在堆中进行动态存储分配。
e. 自动变量(函数中局部变量)以及每次函数调用时所保存的信息都存放在栈中。
f. 命令行参数如 a r g c argc argc 和 a r g v argv argv 这两个变量存放在高地址的命令行变量和环境变量中。



二、进程创建函数fork的使用 fork函数:

头文件:

 #include 

函数原型:

pid_t fork(void);

参数:

无参数
返回值:fork函数调用成功,返回两次
返回值为0, 代表当前进程是子进程
返回值非负数,代表当前进程为父进程
调用失败,返回-1
getpid函数:

头文件:

#include 
#include 

函数原型:

pid_t getpid(void);  //获取子进程pid
pid_t getppid(void); //获取父进程pid

参数:

返回进程pid


编程实现创建子进程,并且分别获取父进程和子进程的pid号:

根据父进程和子进程的pid不同的特点,我们可以在创建进程之前获取一次进程pid,这是父进程的pid,创建进程之后再一次获取进程pid,并通过判断两次pid是否相同判断哪个是父进程pid,哪个是子进程pid。

#include 
#include 
#include 

int main()
{
        pid_t pid;
        pid_t pid2;

        pid = getpid();
        printf("before fork,pid=%dn",pid);

        fork();

        pid2 = getpid();
        printf("after fork,pid=%dn",pid2);

        if(pid == pid2){
                printf("this is a father processn");
        }else
        {
                printf("this is a child process,pid=%dn",pid2);
        }

        return 0;
}

运行结果:




根据 f o r k fork fork 函数的返回值也可以对父子进程进行判断:

#include 
#include 
#include 

int main()
{
        pid_t pid;

        printf("father:pid=%dn",getpid());

        pid = fork();

        if(pid > 0){
                printf("this is a father process,pid=%dn",getpid());
        }else if(pid == 0)
        {
                printf("this is a child process,pid=%dn",getpid());
        }

        return 0;
}

运行结果:



三、进程创建的时候发生了什么事?

  当子进程被创建的时候,到底发生了什么事呢?在早期的Linux内核中,新进程会将代码段、数据段、bss段、堆、栈、命令行参数和环境变量等全部拿来拷贝一份,放入新进程中执行,而随着Linux内核的更新,当新进程被创建的时候,采用写时拷贝的方式运行新进程,在新进程中发生改变的变量新进程会将其拷贝一份作为自己的数据,而其他未被触及到的内容则于父进程进行共享。我们用代码验证一下:

#include 
#include 
#include 

int main()
{
        pid_t pid;
        int data = 10;

        printf("father:pid=%dn",getpid());

        pid = fork();

        if(pid > 0){
                printf("this is a father process,pid=%dn",getpid());
        }else if(pid == 0)
        {
                printf("this is a child process,pid=%dn",getpid());
                data = data + 100;
        }

        printf("data=%dn",data);

        return 0;
}

运行结果:

由此可见,新进程写时拷贝了 d a t a data data 数据作为自己的变量,与父进程区分开来,而其他的变量则与父进程共享。



四、创建新进程的的应用场景及fork总结

fork创建一个子进程的一般目的:

第一个应用最为常见,我们可以用一幅图来说明:

由于我们尚未了解网络编程相关内容,所以我们用一段代码模拟这个应用:

#include 
#include 
#include 

int main()
{
        pid_t pid;
        int data;

        while(1){
        printf("please input a datan");
        scanf("%d",&data);
        if(data == 1){
                pid = fork();
                if(pid > 0){

                }else if(pid == 0)
                {
                        while(1){
                        printf("do net request,pid=%dn",getpid());
                        sleep(3);
                                }
                        }
                }else
                {
                        printf("wait,do nothing!n");
                }
        }
        return 0;
}

运行结果:



f o r k fork fork 总结:



五、vfork函数创建进程

vfork函数 也可以创建进程,与fork有什么区别?

  • 关键区别一:vfork 直接使用父进程存储空间,不拷贝。
  • 关键区别二:vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。

下面我们写段代码验证一下:

这是用 f o r k fork fork 函数创建进程:

#include 
#include 
#include 

int main()
{
        pid_t pid;

        pid = fork();

        if(pid > 0){
                while(1){
                        printf("this is a father process,pid=%dn",getpid());
                        sleep(1);
                }
        }else if(pid == 0)
        {
                while(1){
                        printf("this is a child process,pid=%dn",getpid());
                        sleep(1);
                }
        }

        return 0;
}

运行结果:

这是用 v f o r k vfork vfork 函数创建进程:

#include 
#include 
#include 
#include 

int main()
{
        pid_t pid;
        int cnt = 0;

        pid = vfork();

        if(pid > 0){
                while(1){
                        printf("this is a father process,pid=%dn",getpid());
                        sleep(1);
                }
        }else if(pid == 0)
        {
                while(1){
                        printf("this is a child process,pid=%dn",getpid());
                        sleep(1);
                        cnt++;
                        if(cnt == 3){
                                exit(0);
                        }
                }
        }

        return 0;
}

运行结果:

由此可见,当用 f o r k fork fork 函数创建进程的时候,父子进程会被调度交替运行,而当用 v f o r k vfork vfork 函数创建进程的时候,子进程会不断执行,直到子进程被执行完成后,父进程才会执行。



六、进程退出

正常退出:

1. Main函数调用return
2. 进程调用exit(),标准c库
3. 进程调用_exit()或者_Exit(),属于系统调用
补充:
1. 进程最后一个线程返回
2. 最后一个线程调用pthread_exit

异常退出:

1. 调用abort
2. 当进程收到某些信号时,如ctrl+C
3. 最后一个线程对取消(cancellation)请求做出响应

函数原型:


这里建议使用 e x i t ( ) exit() exit() 函数进行退出,因为 e x i t ( ) exit() exit() 是对 e x i t ( ) _exit() e​xit() 和 E x i t ( ) _Exit() E​xit() 函数的封装, e x i t ( ) exit() exit() 会对缓存区先进行冲刷,然后再进行退出。



七、父进程等待子进程退出

  在上面我们实现了子进程退出,父进程继续执行,但是当创建一个子进程并且子进程执行完以后,父进程需要知道子进程的状态,如果子进程退出状态不被首义,它就会变成僵尸进程(Zombie),上面的代码执行完毕后各进程的状态如图:

这就涉及到父进程等待子进程退出并收集子进程的退出状态, w a i t wait wait 函数就是到父进程等待子进程退出并收集子进程状态的函数,但是对于子进程的退出状态,需要 W E X I T S T A T U S WEXITSTATUS WEXITSTATUS 宏对状态进行解析才能收到正确的子进程退出状态。

相关函数和宏:

status参数:
是一个整型数指针
非空:
子进程退出状态放在它所指向的地址中。
空:
不关心退出状态


编程实现:

#include 
#include 
#include 
#include 

int main()
{
        pid_t pid;
        int cnt = 0;
        int status = 10;

        pid = fork();

        if(pid > 0){

                wait(&status);
                printf("child quit,child status = %dn",WEXITSTATUS(status));
                while(1){
                        printf("cnt=%dn",cnt);
                        printf("this is a father process,pid=%dn",getpid());
                        sleep(1);
                }
        }else if(pid == 0)
        {
                while(1){
                        printf("this is a child process,pid=%dn",getpid());
                        sleep(1);
                        cnt++;
                        if(cnt == 5){
                                exit(3);
                                }
                }
        }

        return 0;
}

运行结果:

![在这里插入图片描述](https://img-blog.csdnimg.cn/ed9a68f51fbe49dc87b0c4021920460e.png

可以看出,当子进程结束后,父进程仍在运行,并且收集子进程退出状态为 3 3 3 ,子进程并没有变成僵尸进程,实现了子进程退出状态被收集。


对于父进程等待子进程退出,还有一个函数: w a i t p i d waitpid waitpid 。在上面使用 w a i t wait wait 函数等待子进程退出的过程中,父进程在不断等待子进程退出,它自己不执行,处于阻塞状态,而我们现在提到的 w a i t p i d waitpid waitpid 函数和 w a i t wait wait 函数的区别就是: w a i t wait wait 使调用者阻塞, w a i t p i d waitpid waitpid 有一个选项,可以使调用者不阻塞。即父进程不等待子进程退出和子进程一起执行。

函数原型:

参数解析:


pid > 0最常用,status参数和上面wait函数的参数一样。


孤儿进程(了解,面试):

  父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。

编程验证:

#include 
#include 
#include 
#include 

int main()
{
        pid_t pid;
        int cnt = 0;

        pid = fork();

        if(pid > 0){
                printf("this is father print,pid = %dn",getpid());
        }else if(pid == 0)
        {
                while(1){
                        printf("this is a child print,pid=%d,my father pid=%dn",getpid(),getppid());
                        sleep(1);
                        cnt++;
                        if(cnt == 5){
                                exit(3);
                        }
                }
        }

        return 0;
}

运行结果:



Unix环境高级编程对于进程等待和退出的验证:(了解)


运行结果:



八、exec族函数

exec族函数函数的作用:

  我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。

exec族函数功能:

  在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

函数原型:

#include 
extern char **environ;

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 *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);  //不做重点

返回值:

  exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。

参数说明:

path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。

exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量

execl编程验证:

主程序:

#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execln");
    if(execl("./echoarg","echoarg","abc",NULL) == -1)
    {
        printf("execl failed!n");
    }
    printf("after execln");
    return 0;
}

待执行程序:

#include 

int main(int argc,char *argv[])
{
    int i = 0;
    for(i = 0; i < argc; i++)
    {
        printf("argv[%d]: %sn",i,argv[i]);
    }
    return 0;
}

运行结果:


#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execln");
    if(execl("/bin/ls","ls","-l",NULL) == -1)
    {
        printf("execl failed!n");
    }
    printf("after execln");
    return 0;
}

运行结果:



execlp编程验证:

#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execln");
    if(execlp("ls","ls","-l",NULL) == -1)
    {
        printf("execl failed!n");
    }
    printf("after execln");
    return 0;
}

运行结果:

由此可见, e x e c l p execlp execlp 函数在找可执行文件的时候不需要加文件具体路径,而是直接在第一个参数的地方写可执行文件名,因为它会自己在环境变量中寻找对应程序。



execvp编程验证:

#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execln");

    char *argv[] = {"ps","-l",NULL}; //用这个字符串数组指针argv,代替execlp函数参数中的后三项
    if(execvp("ls",argv) == -1)
    {
        printf("execl failed!n");
    }
    printf("after execln");
    return 0;
}

运行结果:

由此可见, e x e c v p execvp execvp 函数和 e x e c l p execlp execlp 函数唯一的区别就是 e x e c v p execvp execvp 将原本 e x e c l p execlp execlp 函数的后三项合并为一个字符串数组指针传进来。

execv编程验证:

#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execln");

    char *argv[] = {"ps","-l",NULL};
    if(execvp("/bin/ls",argv) == -1)
    {
        printf("execl failed!n");
    }
    printf("after execln");
    return 0;
}

运行结果:


该函数无非就是需要加可执行文件的路径,和不加 p p p 函数的特点相同。不做仔细论述。



exec配合fork使用:(在一个进程中调用另一个程序)

主程序:

#include 
#include 
#include 

int main()
{
        pid_t pid;
        int data;

        while(1){
        printf("please input a datan");
        scanf("%d",&data);
        if(data == 1){
                pid = fork();

                if(pid == 0)
                {
                        execl("./changeData","changeData","config.txt",NULL);
                }
                }else
                {
                        printf("wait,do nothing!n");
                }

        }

        return 0;
}

被调用的程序:

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

int main(int argc, char **argv)
{
        int fdSrc;

        char *readBuf = NULL;

        if(argc != 2){
                printf("param error!n");
                exit(-1);
        }

        fdSrc = open(argv[1],O_RDWR);

        int size = lseek(fdSrc, 0, SEEK_END);
        lseek(fdSrc, 0, SEEK_SET);

        readBuf = (char*)malloc(sizeof(char)*size + 8);

        int n_read = read(fdSrc, readBuf, size);

        char *p = strstr(readBuf, "LENG=");

		p = p + strlen("LENG=");
        *p = '5';

        lseek(fdSrc, 0, SEEK_SET);

        int n_write = write(fdSrc, readBuf, strlen(readBuf));

        close(fdSrc);

        return 0;
}

运行结果:

配置文件被更改前:

更改后:



九、system函数

   s y s t e m system system 函数类似于 e x e c exec exec 族函数,我们用fork函数创建新进程后,也能在新进程中调用 s y s t e m system system 去执行另外一个程序。当进程调用 s y s t e m system system 函数时,该进程被替换为新程序。与 e x e c exec exec 族函数不同的一点是, s y s t e m system system 函数调用完一个程序后,还会返回原来的主程序继续执行完。

函数原型:

#include 

int system(const char *command);

参数:

const char *command:命令字符串
system()函数的返回值如下: 
成功,则返回进程的状态值; 当sh不能执行时,返回127; 失败返回-1; 

编程实现:

#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execln");
    if(system("ls -l") == -1)
    {
        printf("execl failed!n");
    }
    printf("after execln");
    return 0;
}

运行结果:

由此可见, s y s t e m system system 函数的简便之处,而且它所调用的程序执行完以后,主程序还会继续执行,这也是它的特殊之处。



十、popen函数

  popen() 函数用于创建一个管道:其内部实现为调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程这个进程必须由 pclose() 函数关闭。比system在应用中的好处:可以获取运行的输出结果。

函数原型:

#include “stdio.h”

FILE popen( const char command, const char* mode )

参数:

command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。
这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令。
mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。
如果 type 是 “r” 则文件指针连接到 command 的标准输出;
如果 type 是 “w” 则文件指针连接到 command 的标准输入。

如果调用成功,则返回一个读或者打开文件的指针,如果失败,返回NULL,具体错误要根据errno判断。

int pclose (FILE* stream)
参数说明:
stream:popen返回的文件指针
返回值:
如果调用失败,返回 -1

编程实现:

#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
        char ret[1024]  = {0};
        FILE *fp;

        fp = popen("ps","r");
        int nread = fread(ret,1,1024,fp);

        printf("read %d byte,ret=%sn",nread,ret);

        pclose(fp);

        return 0;
}

运行结果:

主程序 p o p e n popen popen 函数调用的程序运行的内容被捕获保存在 r e t ret ret 字符串变量中。



本次关于Linux系统编程中进程的学习笔记就分享到这里。多多分享,多多交流。

自己的命运自己掌控,脚下的路不停步。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1010621.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号