入门Linux系统编程--进程

一、进程

1、进程相关概念

  • 什么是程序,什么是进程,有什么区别?

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

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

a、使用ps指令查看
实际工作中,配合grep来查找程序中是否存在某一个进程
在这里插入图片描述
b、使用top指令查看,类似windows任务管理器
在这里插入图片描述

  • 什么是进程标识符?

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

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

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

在这里插入图片描述

#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>

int main()
{

        pid_t pid;
        pid = getpid();
        printf("my pid is %d\n",pid);
        return 0;
}
                  

输出结果:
在这里插入图片描述

  • 什么叫父进程,什么叫子进程

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

  • C语言的存储空间是如何分配?

在这里插入图片描述
在这里插入图片描述

2、创建进程函数fork的使用

在这里插入图片描述
在父进程中,fork()之后,创建一个新进程,为子进程

  • 代码:
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>

int main()
{
        pid_t pid;
        pid_t pid2;

        pid = getpid();
        printf("before fork:pid= %d\n",pid);

        fork();
		//下面会被执行两次
        pid2 = getpid();
        printf("after fork:pid = %d\n",pid2);

        if(pid == pid2)
        {
                printf("this is father print\n");
        }
        else{
                printf("this is child print,child pid = %d\n",getpid());//fork()创建新进程,pid会和刚开始的不同
        }
        return 0;
}
                  

输出结果:
在这里插入图片描述

  • fork()后,父进程返回值为子进程的进程ID,子进程返回值为0
    在这里插入图片描述
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>

int main()
{
        pid_t pid;
        pid = fork();

        if(pid >0)//fork后,父进程返回值为子进程的进程ID
        {
                printf("this is father print,fork_retvalue = %d,pid = %d\n",pid,getpid());
        }
        //fork后,子进程返回值为0
        else{

                printf("this is child print,fork_retvalue = %d,pid = %d\n",pid,getpid());
        }
        while(1);

        return 0;
}


输出结果:
在这里插入图片描述

3、创建进程函数fork的使用补充

  • 实现代码:
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>

int main()
{
        pid_t pid;
        pid_t pid2;
        pid_t retpid;
        
        pid = getpid();
        printf("before fork:pid = %d\n",pid);
        
        retpid = fork();
        pid2 = getpid();
		printf("after fork:pid = %d\n",pid2);
		
        if(pid == pid2)
        {
                printf("this is father print,fork_retvalue = %d\n", retpid);
        }
       
        else{

                printf("this is child print,fork_retvalue = %d\n", retpid);
        }
        while(1);

        return 0;
}

输出结果:
在这里插入图片描述
早期的Linux,调用fork函数以后,是把整个程序的存储空间进行拷贝。后面Linux内核的技术更新,为了提高效率,是以写时拷贝的方式各自拥有一份数据。子进程复制父进程的数据段,栈和堆,父子进程共享正文段。也就是说,对于程序中的数据,子进程要复制一份,但是对于指令,子进程并不复制,而是和父进程共享。(如果子进程对数据段中的变量不做修改时,则采用共享的原则,只有子进程对数据段中的变量进行修改的时候,才会在子进程的地址空间复制一份)

  • 测试代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

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

    pid = fork();

    if(pid > 0){

       	printf("this is father print, pid=%d\n",getpid());

    }else if(pid == 0){
	printf("this is child print, pid=%d\n",getpid());
        data = data + 100;
    }

     printf("data = %d\n",data);
     return 0;

}

输出结果:
在这里插入图片描述

4、创建新进程的实际应用场景及fork总结

在这里插入图片描述

  • 测试代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
 pid_t pid;
 int data;

 while(1){
          printf("plese input data\n");
          scanf("%d",&data);

          if(data == 1){

                pid = fork();
		
		if(pid ==0){
                     while(1){
		             printf("child pid is %d\n",getpid());
		             sleep(1);
                                   }
                       }



          }else{
                printf("data is not 1 \n");
          }

 }

 return 0;
}             

输出结果:
在这里插入图片描述
在这里插入图片描述

5、vfork创建进程

  • vfork函数也可以创建进程,与fork有什么区别:
    1、vfork直接使用父进程存储空间,不拷贝
    2、vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。

  • 测试代码:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>

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

        if(pid > 0)
        {
                while(1){
                        printf("cnt=%d\n",cnt);
                        printf("this is father print,pid=%d\n",getpid());
                        sleep(1);
                }
        }
        else if(pid == 0){

                while(1){
                        printf("this is child print,pid = %d\n",getpid());
                        sleep(1);
                        cnt++;
                        if(cnt == 3){
                                exit(0);
                                }
                        }
                }
        return 0;
}      

输出结果:
可以看出,子进程是先执行的,调用exit退出后,父进程才执行
在这里插入图片描述

6、进程退出

  • 正常退出
    1、Main调用return
    2、进程调用函数用exit(),标准c库
    3、进程调用_exit()或者_Exit(),属于系统调用
    4、进程最后一个线程返回
    5、最后一个线程调用pthread_exit

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

在这里插入图片描述

  • exit实际上是对_exit和_Exit的封装,一般建议用exit

在这里插入图片描述
在这里插入图片描述

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

  • 父进程等待子进程退出,并收集子进程的退出状态

  • 子进程退出状态不被收集,会变成僵死进程(僵尸进程)

  • 僵尸进程
    子进程退出状态不被收集,将会变成僵尸进程
    在这里插入图片描述

  • 相关函数

在这里插入图片描述
在这里插入图片描述

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

  • wait与waitpid的区别:

    wait使调用者阻塞,waitpid有一个选项,可以使调用者不阻塞
    如果其所有子进程都还在运行,则阻塞
    如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回
    如果它没有任何子进程,则立即出错返回

  • 对于waitpid函数中pid参数的作用解释如下:
    pid==-1 等待任一子进程,这一方面,wait与waitpid等效
    pid>0 等待其进程ID与pid相等的子进程
    pid==0 等待其组ID等于调用进程组ID的任一子进程
    pid<-1 等待其组ID等于pid绝对值的任一子进程

在这里插入图片描述

  • 测试代码:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/wait.h>

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

        int status = 10;//初始化status值

        pid = fork();

        if(pid > 0)
        {

                wait(&status);//等待子进程结束,收到子进程的退出状态
                printf("child quit.child status = %d\n",WEXITSTATUS(status));//打印子进程exit的返回值
                while(1){
                        printf("cnt=%d\n",cnt);
                        printf("this is father print,pid=%d\n",getpid());
                        sleep(1);

                }

        }
        else if(pid == 0){

                while(1){
                        printf("this is child print,pid = %d\n",getpid());
                        sleep(1);
                        cnt++;
                        if(cnt == 3){
                                exit(6);//子进程退出

                                }
                        }
                }

        return 0;
}
~  

输出结果:
在这里插入图片描述

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

  • 测试代码

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/wait.h>

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

        int status = 10;//初始化status值

        pid = fork();

        if(pid > 0)
        {
                        printf("this is father print,pid=%d\n",getpid());
                        sleep(1);

        }
        else if(pid == 0){

                while(1){
                        printf("this is child print,child's pid = %d,father's pid = %d\n",getpid(),getppid());
                        sleep(1);
                        cnt++;
                        if(cnt == 3){
                                exit(6);//子进程退出

                                }
                        }
                }

        return 0;
}

输出结果:
可以看出init进程收留孤儿进程,变成孤儿进程的父进程
在这里插入图片描述

8、exec族函数

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

函数族:
exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe

  • 函数族:
#include <unistd.h>
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环境变量,在它所指定的各目录中搜寻可执行文件。

带l的一类exac函数(l表示list),包括execl、execlp、execle,要求将新程序的每个命令行参数都说明为 一个单独的参数。这种参数表以空指针结尾。

//文件execl.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

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

//文件echoarg.c
#include <stdio.h>

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

在这里插入图片描述

二、往期文章

1、入门Linux系统编程–文件
2、入门Linux系统编程–进程
3、入门Linux系统编程–线程
4、入门Linux系统编程–进程间通信
5、入门Linux系统编程–网络编程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

秃秃秃秃哇

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

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

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

打赏作者

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

抵扣说明:

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

余额充值