进程基础知识

一. 进程和程序

程序:死的。只占用磁盘空间。
进程:活的。运行起来的程序。占用内存、cpu等系统资源。
并发和并行:**并行是宏观上,**指两个或多个事件在同一个时间段内的概念。 **并发是微观上串行,**不是一个时间点,而是一个时间段内的概念

二. PCB控制块

每个进程在内核中都有一个进程控制块(pcb)来维护进程的相关信息。

PCB进程控制块:
	进程id
	文件描述符表
	进程状态:	初始态、就绪态、运行态、挂起态、终止态。
	进程工作目录位置
	*umask掩码 (进程的概念)
	信号相关信息资源。
	用户id和组id
ps aux 返回结果里,第二列是进程id

进程的基本状态有5种:初始态(进程的准备阶段)、就绪态、运行态、挂起态、终止态。
在这里插入图片描述

三. 进程的函数操作

1. fork函数:父进程调用fork函数创建子进程

父子进程相同处:data段、text段、堆、栈、环境变量、全局变量、宿主目录位置、进程工作目录位置、信号处理方式
不同处:进程id、返回值、各自的父进程、进程创建时间、闹钟、未决信号集
全局变量:读时共享,写时复制

pid_t fork(void)
参数:void
返回值:fork函数返回父子进程标志:
	   子进程:返回值为0
	   父进程:返回值大于0
获取父子进程的两个函数:
pid_t getpid()		获取当前进程id
pid_t getppid()		获取当前进程的父进程id
两个函数的返回值都是进程id(%d)

父子进程的注意事项:子进程创建成功后,父进程执行到哪,子进程就从哪执行;父子进程的执行顺序不一定,谁抢到cpu谁执行。
示例:循环创建多个子进程:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
 
int counter = 100;
 
int main(int argc, const char* argv[])
{
    pid_t pid;
    int i=0;
    for(i=0; i<3; i++)
    {
        pid = fork();
        //重点:如果不将子进程退出循环体,我们知道子进程会从这段代码后开始运行,也会循环产生多个自己的子进程。
        if(pid == 0)
        {
            break;
        }
    }
    // 父进程
    if(i == 3)
    {
        counter += 100;
        printf("parent process, pid = %d, ppid = %d, %d\n", getpid(), getppid(), counter);
        //等待子进程结束,回收子进程。
        sleep(1);
    }
    // 子进程
    else if(i == 0)
    {
        // 1th
        counter += 200;
        printf("child process, pid = %d, ppid = %d, %d\n", getpid(), getppid(), counter);
    }
    else if(i == 1)
    {
        // 2th
        counter += 300;
        printf("child process, pid = %d, ppid = %d, %d\n", getpid(), getppid(), counter);
    }
    else if(i == 2)
    {
        // 3th
        counter += 400;
        printf("child process, pid = %d, ppid = %d, %d\n", getpid(), getppid(), counter);
    }
    return 0;
}

2. 进程相关的命令

ps:
ps aux | grep "xxx"
pa aux | grep "xxx"
kill
查看信号:kill -l
杀死某个进程:kii -9 (SIGKILL)PID

3. exec函数族

功能:父子进程执行不相干的操作,能够替换进程地址空间中的源代码.txt段
这样可以实现:执行另外一个程序不需要创建额外的地址空间,可以在一个运行的程序中调用另外一个程序。
在这里插入图片描述

execl函数:
int execl(const char *path, const char *arg, ……)
参数:
    path:要执行的程序的绝对路径
    变参arg:要执行的程序需要的草书
           第一arg:占位,值不会影响后面的程序执行
           后边的arg:命令的参数
           参数写完之后:NULL
           一般执行自己写的程序
返回值:
      成功:无返回值
      失败:-1

示例代码:

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

int main(int argc, const char* argv[])
{
    printf("hello, world\n");
 
    for(int i=0; i<3; ++i)
    {
        printf("parent i = %d\n", i);
    }
 
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork error");
        exit(1);
    }
    // 子进程执行程序
    if(pid == 0)
    {
        // execl("hello", "xxxx",  NULL);
        //execl("/home/kevin/hello", "xxxx",  NULL);
        //NULL这里是哨兵,表示可变变量的输入已经终止
        //ls程序是用的子进程的地址空间
        execl("/bin/ls","ls","-lah",NULL);
        perror("execl");
        exit(1);
    }
    for(int i=0; i<3; ++i)
    {
        printf(" i = %d\n", i);
    }
    return 0;
}

其他exec函数总结:

int execlp(const char *file, const char *arg,)
参数:
	const char*: 要加载的程序名字,该函数需要配合PATH环境变量来使用,
	当PATH所有目录搜素后没有参数1则返回出错。
其他和execl函数类似。
该函数通常用来调用系统程序。如ls、date、cp、cat命令。
execlp这里面的p,表示要借助环境变量来加载可执行文件

int execle(const char *path, const char *arg, ……, char *const envp[])
参数:
	path:执行程序的绝对路径 /home/lj/hello
	arg:执行程序的参数
	envp:用户自己指定的搜索目录,替代PATH

int execve(const char *path,char *const argv[], char *const envp[])
……

4. 孤儿进程和僵尸进程

孤儿进程
父进程先于子进终止,子进程沦为“孤儿进程”,会被 init 进程领养。
僵尸进程
子进程终止,父进程尚未对子进程进行回收,在此期间,子进程为“僵尸进程”。 kill 对其无效。这里要注意,每个进程结束后都必然会经历僵尸态,时间长短的差别而已。子进程终止时,子进程残留资源PCB存放于内核中,PCB记录了进程结束原因**,进程回收就是回收PCB。回收僵尸进程,得kill它的父进程,让孤儿院去回收它。**
在这里插入图片描述在这里插入图片描述

5. 进程回收函数

wait函数:回收子进程退出资源, 阻塞回收任意一个。

pid_t wait(int *status)
	参数:(传出) 回收进程的状态。为传出参数
		WIFEXITED(status):为非0,进程正常退出
		WIFEXITED(status) :为真 调用 WEXITSTATUS(status)得到子进程 退出值。
	获取导致子进程异常终止信号:
		WIFSIGNALED(status):为真 调用 WTERMSIG(status)得到 导致子进程异常终止的信号编号。
	返回值:成功: 回收进程的pid
		   失败: -1, errno
函数作用1:	阻塞等待子进程退出
函数作用2:	清理子进程残留在内核的 pcb 资源
函数作用3:	通过传出参数,得到子进程结束状态

一个进程终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或者waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在shell中用特殊变量$?查看,因为shell是它的父进程,当它终止时,shell调用wait或者waitpid得到它的退出状态,同时彻底清除掉这个进程。
示例
在这里插入图片描述
在这里插入图片描述
waitpid函数: 指定某一个进程进行回收。可以设置非阻塞。
waitpid(-1, &status, 0) == wait(&status);

pid_t waitpid(pid_t pid, int *status, int options)
参数:
	pid:指定回收某一个子进程pid
		> 0: 待回收的子进程pid
		-1:任意子进程
		0:同组的子进程。
	status:(传出) 回收进程的状态。
    options:WNOHANG 指定回收方式为,非阻塞。
返回值:
	> 0 : 表成功回收的子进程 pid
    0 : 函数调用时, 参3 指定了WNOHANG, 并且,没有子进程结束。
    -1: 失败。errno

一次wait/waitpid函数调用,只能回收一个子进程。父进程产生了多个子进程,wait会随机回收一个。

示例:指定一个子进程进行回收:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/wait.h>  
#include <pthread.h>  
	  
int main(int argc, char *argv[])  
{  
    int i;  
    pid_t pid, wpid, tmpid;  	  
	for (i = 0; i < 5; i++) 
	{         
		pid = fork();  
	    if (pid == 0) 
	    {       
	    	// 循环期间, 子进程不 fork   
			 break;  
        }  
        if (i == 2) 
        {  
        	tmpid = pid;  
	        printf("--------pid = %d\n", tmpid);  
	    }  
	 }  
	 if (5 == i) 
	 {       
	 	// 父进程, 从 表达式 2 跳出  
		sleep(5);  
	    //wait(NULL);      // 一次wait/waitpid函数调用,只能回收一个子进程.  
	    //wpid = waitpid(-1, NULL, WNOHANG); //回收任意子进程,没有结束的子进程,父进程直接返回0   
	    //wpid = waitpid(tmpid, NULL, 0);   //指定一个进程回收, 阻塞等待  
	    printf("i am parent , before waitpid, pid = %d\n", tmpid);  
        //wpid = waitpid(tmpid, NULL, WNOHANG);   //指定一个进程回收, 不阻塞  
	    wpid = waitpid(tmpid, NULL, 0);         //指定一个进程回收, 阻塞回收  
	    if (wpid == -1) {  
	        perror("waitpid error");  
	        exit(1);  
        }  
	    printf("I'm parent, wait a child finish : %d \n", wpid);  
	  
    } else {            // 子进程, 从 break 跳出  
        sleep(i);  
	    printf("I'm %dth child, pid= %d\n", i+1, getpid());  
	    }    
    return 0;  
}  

运行结果:
在这里插入图片描述
循环回收多个子进程:

// 回收多个子进程  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/wait.h>  
#include <pthread.h>  

int main(int argc, char *argv[])  
{  
    int i;  
    pid_t pid, wpid;  
  
    for (i = 0; i < 5; i++) {         
        pid = fork();  
        if (pid == 0) {       // 循环期间, 子进程不 fork   
            break;  
        }  
    }  
  
    if (5 == i) {       // 父进程, 从 表达式 2 跳出  
        /* 
        while ((wpid = waitpid(-1, NULL, 0))) {     // 使用阻塞方式回收子进程 
        printf("wait child %d \n", wpid); 
    } 
        */  
        while ((wpid = waitpid(-1, NULL, WNOHANG)) != -1) {  //使用非阻塞方式,回收子进程. 
            if (wpid > 0) {  
                printf("wait child %d \n", wpid);  
            } else if (wpid == 0) {  
                sleep(1);  
                continue;  
            }  
        }  
  
    } else {            // 子进程, 从 break 跳出  
        sleep(i);  
        printf("I'm %dth child, pid= %d\n", i+1, getpid());  
    }   
    return 0;  
}  

编译运行,结果如下:
在这里插入图片描述

6. 进程间通信的方法

进程间通信(IPC):interprocess Communication
常用的有四种方式:(后面的章节介绍)
(1)管道-简单
(2)信号-系统开销小
(3)共享映射区-有无血缘关系的进程间通信都可以
(4)本地套接字-稳定

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值