Linux之进程和线程(详解-上)


原创不易,还请给个三连支持一下鸭~
在这里插入图片描述

一、进程的概念

一个程序文件,只是一堆待执行的代码和部分待处理的数据,它们只有被加载到内存中,然后让CPU逐条执行其代码,根据代码做出相应的动作,才形成一个真正“活的”、动态的进程。因此,进程是一个动态变化的过程,是一出有始有终的戏,而程序文件只是这一系列动作的原始蓝本,是一个静态的剧本。
总:一个程序,当运行时,那么系统中都会产生一个对应的进程,动态的概念。

二、进程的内存结构

进程的内存是一种虚拟内存,本质是对物理内存的映射。包括:内核、栈、堆、数据段、代码段 和 不可访问段。如下图:
在这里插入图片描述
为了更好地展示程序进程的关系,,如下图所示:
在这里插入图片描述
总的来说:进程 = 代码块 + 数据块 + 进程控制块

三、进程的生老病死

如下图:
在这里插入图片描述

(1) 从出生看,一个进程的诞生,由其父进程调用fork()开始的。
(2) 进程刚出生时,处于task_running状态,就绪态执行态都属于task_running, 前者在进程队列中排队,后者是占用CPU执行中。内核调用函数sched()时,就绪态会转变成执行态,所以sched()也被称为进程调度器。可以使用时间片模式或者高优先级抢占模式,亦或者两者的混合决定由谁来占用CPU。
(3) 进程处于就绪态时,可能由于某些临界资源不可得,而被置睡眠态/挂起态,称为:task_interruptible(浅睡眠) 和 task_uninterruptible(深度睡眠) 。两者的区别是,浅睡眠可以被信号唤醒,而深度睡眠不可被唤醒,。浅睡眠在要等待的资源变得可得的时候,又会被系统置为task_running状态重新排队。
(4) 当进程收到SIGSTOP 或 SIGTSTP 的信号时,状态会被置为task_stopped(暂停态)。该状态下,进程不参与cpu调度,但也不释放资源,直到收到SIGCONT信号后被重新置就绪态。当进程被追踪时(代码调试),进程的状态是task_traced,和暂停态是类似的。
(5) 进程的死亡可以有很多方式,可以是寿终正寝的正常退出,亦可被异常杀死。可能是在main函数中return,也可能是执行exit()/Exit()/_exit(),再可能是最后一个线程执行pthread_exit(),再再可能是直接被信号杀死。不管如何,最后会由监尸官(内核)发一道死亡证明( do_exit() ),将该进程置为exit_zombie状态,也叫僵尸态
(6) 僵尸态的进程,其进程控制块里面有他的死亡信息,等待父进程进行回收。父进程可调用wait()/waitpid()来查看子进程的“死亡信息”,比如子进程是被哪个信号杀死的,死亡后的返回值是多少等等。父进程处理完这些信息后,会将尸体送进火葬场,即将子进程置为exit_dead状态(死亡态),此时系统就回收了子进程的进程控制块了(先前的其它内存资源在僵尸态时已被回收)。

值得深挖的是:如果子进程的父进程先死,那怎么办?谁来处理子进程的尸体?这时候就由父进程的父进程,也就是祖父进程回收。那祖父进程也死了呢?套娃是吧,那就由祖父的父进程回收。最后有一个祖宗是长生不死的(最少与系统共存),那就是init进程,如果某个进程的家族死光了,那最后就由祖宗回收吧。

四、Linux系统编程之进程

进程创建fork

             #include <unistd.h>
             pid_t fork();
                返回值:   >0  表示父进程,此时返回值中存放的是子进程的id号
                          ==0 表示子进程
                          -1  表示失败
          fork函数的特点
              第一个: 一次调用,两次返回,分别表示父子进程
              第二个: fork创建子进程的时候,会给子进程分配独立的内存空间,同时子进程会复制父进程的所有资源(变量,代码)
          vfork函数的特点
              第一个: 父子进程共享数据段
              第二个: 子进程一定先于父进程运行
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

int main(){
    pid_t pid;
    int x = 0;
    printf("我是fork之前的语句\n");  //在fork之前,除了变量,在子进程中都不会调用
    pid = fork();
    if(pid > 0){
        printf("我是父进程\n");
        printf("x:%d\n",x); // 0
        sleep(1);
    }
    else if(pid == 0){
        printf("我是子进程\n");
        printf("x:%d\n",x); //0
        sleep(1);
    }
    int k = 66;
    printf("k:%d\n",k);
    printf("我在结尾,不知道父子会不会都调用\n"); //父子进程都会调用
    wait(NULL); //父进程回收子进程,如果子进程没有执行完,会阻塞
    return 0;
}

在这里插入图片描述
可以看到,fork之前的printf由父进程调用,子进程不调用,当是父进程的变量会复制到子进程中,所以父进程和子进程都可以访问x,但是这两个x是独立的,父进程改变x不会同步到子进程,反过来亦然。

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

int main(){
    pid_t pid;
    int x = 0;
    printf("我是fork之前的语句\n");  //在fork之前,除了变量,在子进程中都不会调用
    // 如果想要父子进程共享x,可使用vfork()
    pid = fork();
    if(pid > 0){
        printf("我是父进程\n");
        printf("x:%d\n",x); // 0
		x += 1;
		printf("我是父进程,更改x后:%d\n", x);
        sleep(1);
    }
    else if(pid == 0){
        printf("我是子进程\n");
        printf("x:%d\n",x); //0
		x += 10;
		printf("我是子进程,更改x后:%d\n", x);
        sleep(1);
    }
    wait(NULL); //父进程回收子进程
    return 0;
}

在这里插入图片描述

进程的结束和回收

结束进程:
               exit()
               _exit()
           区别:
              exit结束进程的时候会刷新缓冲区,但是_exit()不会刷新缓冲
              正常情况下:\n和return语句都能帮我们刷新缓冲区
           return和exit的区别:
              区别一:return 是关键字,exit()是函数
              区别二:return结束函数,返回返回值
                      exit()结束进程  
进程的回收
           #include <sys/wait.h>
            pid_t wait(int *stat_loc);
               返回值:成功 回收到的那个子进程的id号
                      失败 -1
               参数(重点):stat_loc --》存放进程退出时候的状态信息
                       进程的退出值仅仅只是状态信息的一部分,状态信息还包含其它内容(子进程是正常退出还是异常退出,是被哪个信号弄死的)
                特点:阻塞父进程,等待子进程的退出

           pid_t waitpid(pid_t pid, int *stat_loc, int options);
              返回值:成功 回收到的那个子进程的id号
                      失败 -1
                参数:pid --》  <  -1  回收进程组id是pid绝对值中的某个进程
                                       比如: waitpid(-10000,) 回收进程组ID是10000的这个组里面的某个进程
                                == -1  回收任意一个进程
                                       比如: waitpid(-1,);
                                == 0   回收本进程组中的某个进程
                                       比如: waitpid(0) 
                                >  0   指定回收进程id是pid的这个进程
                                       比如:  waitpid(10000,); 回收id是10000的这个进程
                     options --》 WNOHANG  非阻塞等待,父进程在退出的时候,如果子进程还没有退出,那么父进程不会阻塞,也不会去回收,直接退出
                                  0        阻塞等待
                补充:进程组就是多个进程组成的一个集合

获取进程的ID

// 获取当前进程的id号
pid_t getpid(void);
// 获取父进程的id
pid_t getppid(void);

system 和 exec

我们创建子进程时,一般不会在子进程中直接写丰富的代码来执行任务,而是使用system或者exec来执行其它的可执行程序。
比如:
main1.c

#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(){
    pid_t pid;
    pid = fork();
    if(pid > 0){
        printf("我是父进程,我的子进程是%d\n",pid);
        sleep(1);
    }
    else if(pid == 0){
        printf("我是子进程\n");
        // 注意,这里有父进程、子进程和system调用的进程,三个进程
		system("./main2");
		// 而使用exec函数簇时,不会产生新的进程,就只有父子进程
		//execl("./main2","./main2",NULL);
        sleep(1);
    }
    wait(NULL); //父进程回收子进程
    return 0;
}

main2.c

#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
int main(){
	pid_t myid = getpid();
	printf("进程%d在执行中···", myid);
	return 0;
}

在这里插入图片描述
下面来详细讲解 system函数exec 函数族的用法
system:

	头文件:#include <stdlib.h>
       int system(const char *command)  
            参数: command --》你要执行的shell命令或者可执行程序  
       用法一:执行shell命令
       用法二:执行另外一个可执行程序

execl:

int main()
{
	//execl("/bin/ls","ls","-l",NULL); //ls -l 
	//execl("/bin/cp","cp","1.txt","/home/gec",NULL); // cp 1.txt /home/gec
	execl("/mnt/hgfs/code/hello","./hello",NULL); //./hello
}

execv:

int main()
{
	//定义指针数组
	//char *arg[]={"ls","-l",NULL};
	//execv("/bin/ls",arg);
	//char *arg[]={"cp","1.txt","/home/gec",NULL};
	//execv("/bin/cp",arg);
	char *arg[]={"./hello",NULL};
	execv("/mnt/hgfs/code/hello",arg);
}

execle:

int main()
{
	//定义指针数组
	char *envp[]={"export","PATH=/mnt/hgfs/code:$PATH",NULL};
	//execle("/bin/ls","ls","-l",NULL,envp);
	//execle("/bin/cp","cp","1.txt","/home/gec",NULL,envp);
	execle("/mnt/hgfs/code/hello","./hello",NULL,envp);
}

类似的还有:execlp、execvp、execvpe,功能差不多(有时候感觉弄这么多类似的接口真的莫名其妙)。

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值