欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

进程基础知识

程序员文章站 2022-05-22 19:21:42
...

一. 进程和程序

程序:死的。只占用磁盘空间。
进程:活的。运行起来的程序。占用内存、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)本地套接字-稳定