Linux高级编程_28_进程

news2024/11/24 0:25:53

文章目录

  • 进程
    • 并行与并发
    • 单道与多道程序
    • 进程控制块(PCB)了解
    • PCB存储位置
    • 进程号:
      • 进程号:(PID)
      • 进程组号:(PGID)
      • 父进程号:(PPID)
    • fork函数
  • 多进程创建
    • 进程状态
    • 进程资源的回收
      • 1 wait函数:
      • 2 exit 与 _exit()
        • exit函数
        • _exit函数【*】
      • 3 WIFEXITED(status)与WEXITSTATUS(status)
      • 4 waitpid函数 【*】
  • 多进程回收
      • 5 atexit函数
  • 特殊进程
    • 僵尸进程
    • 孤儿进程
    • 守护进程
  • 进程补充:
    • 1 终端
    • 2 进程组
    • 3 会话
    • **4 守护进程**
  • vfork
  • exec函数簇 【*】
    • exec函数族与vfork结合
  • system

进程

概述: 一个正在进行的程序

并行与并发

  • 并行: 执行的程序在不同CPU上同时执行
  • 并发: 一个CPU 多个进程交替执行,因为交替速度很快,所以从宏观上看是同时执行的,但本质就是交替执行。

单道与多道程序

单道程序设计:所有进程一个一个排队执行。若A阻塞,B只能等待,即使CPU处于空闲状态,已弃用

多道程序设计: 在计算机内存中同时存放几道相互独立的 程序 , 它们在管理程序控制之下,相互穿插的运行(并行和并发)

进程控制块(PCB)了解

进程运行时,内核为进程每个进程分配一个PCB(进程控制块),维护进程相关的信息,Linux内核的进程控制块是
task_struct结构体

task_struct结构体

在 /usr/src/linux-headers-xxx/include/linux/sched.h 文件中可以查看task_struct结构体定义

其内部成员有很多,我们掌握以下部分即可:
进程 id: c语言使用pid_t的类型表示, 其实就是一个非负整数
进程的状态:有就绪运行、挂起、停止等状态。

PCB存储位置

在内核中

在这里插入图片描述

进程号:

当前进程的 ID 就是进程号 由系统分配

每一个进程都由一个进程号来标识 其类型为 pid_t 进程号的范围: 0~32767

进程号总是唯一的 但是可以重用 当一个进程结束后,其进程号就可以再次使用了

进程号 为 0 和 1 的进程由内核创建

进程号为 0 的进程通常是 **调度进程,**常被称为交换进程(swapper)。进程号为1 的进程 通常是 init 进程。

除调度进程(0号进程)外,在 linux 下面所有的进程都由进程 init [1]进程直接或者间接创建。

进程号:(PID)

​ 标识进程的一个非负整型数

获取当前进程的进程号 :

​ pid_t getpid( );

​ pid_t 是进程号 是 int 型

进程组号:(PGID)

​ 多个进程在同一组中,该组就是进程组

​ 该组中第一个进程就是组长进程,该组长的进程号就是该进程组的 id

​ 由 进程A 开启的子进程 默认为进程A为同组进程 ,此时A 就是组织进程

获取指定进程的进程组号 :

​ pid_t getpgid ( int pid );

父进程号:(PPID)

​ 由进程A 开启 B 此时进程A 就是B 的父进程

​ 进程B 就是 A 的子进程

获取父进程的进程号 :

​ pid_t getppid();

当进程A 优先 于 进程B 先销毁 此时B就是孤儿进程 (父先挂子存在)此时由1进程充当 父进程

示例:

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
using namespace std;
int main(int argc, char const *argv[])
{
 //获取当前进程id
 int  pid = getpid();
 cout<<"当前进程号为"<<pid<<endl;
 //获取同组进程id
 int pgid = getpgid(pid);
 cout<<"当前进程组为"<<pgid<<endl;

 //获取父进程id
 int  ppid = getppid();
 cout<<"当前进程父进程id为"<<ppid<<endl;


 //创建子进程
 int xxz_id = fork(); 
 if(xxz_id == 0)
 {
     cout<<"子进程号"<<getpid()<<endl;
     cout<<"子进程的父进程号"<<getppid()<<endl;
     cout<<"当前进程组为"<<getpgid(getpid())<<endl;
     sleep(5);
     exit(0);
 }
 else if (xxz_id >0 )
 {
     wait(NULL);
 }
 return 0;
}

在这里插入图片描述

fork函数

作用: 创建进程

语法:

pid_t fork();
返回值:
 如果在子进程中   返回值就是0,
 如果在父进程中   就是创建的子进程id,
 -1创建失败

fork失败的两个主要原因是:

  • 1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
  • 2)系统内存不足,这时errno的值被设置为ENoM

注意:
1 子进程会从fork函数后开始执行

​ 2 子进程共享父进程所有内容,不包括进程号,父进程号等

	使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间。
	地址空间:包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。
	子进程所独有的只有它的进程号,计时器等。

因此,使用fork 函数的代价是很大的。共享相当于 复制了一份

​ 3 谁创建谁回收

示例1

子进程会从fork函数后开始执行

#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
using namespace std;
int main(int argc, char const *argv[])
{
     // 只有父进程执行
     printf("这是一行废话,当前进程 %d\n", getpid());
     // 子进程会从fork函数后开始执行
     int pid = fork();                            // 创建子进程
     printf("hello 进程 当前进程%d\n", getpid()); // 执行两次
     return 0;
}

在这里插入图片描述

示例2 :

子进程共享父进程所有内容,不包括进程号,父进程号等

#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
using namespace std;
int main(int argc, char const *argv[])
{
     printf("这是一行废话,当前进程");
     // 子进程共享父进程所有内容,不包括进程号,父进程号等 包括缓冲区
     int pid = fork(); // 创建子进程
     printf("hello 进程 当前进程%d\n", getpid());
     return 0;
}

在这里插入图片描述

示例3:

#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
int main(int argc, char const *argv[])
{
     char *str = "这是一行废话";
     // 此时输出的内容直接在屏幕上不在缓冲区
     write(1, str, strlen(str));
     int pid = fork();
     printf("hello 进程 当前进程%d\n", getpid());
     return 0;
}

在这里插入图片描述

引入多进程创建

在这里插入图片描述

也就是对下面这句话的理解

使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间。
地址空间:包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。
子进程所独有的只有它的进程号,计时器等。

在这里插入图片描述

那这样要是创建多进程呢

多进程创建

解决方案:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
// 多线程创建 但是没人 回收
int main(int argc, char const *argv[])
{
    for (int i = 0; i < 4; i++)
    {
        //创建子线程
        int pid = fork();
        // 父进程中 pid > 0
        // 子进程中 pid = 0
        if (pid == 0)  
        {
            // 子进程代码
            printf("子进程 %d 的进程号是 %d\n", i, getpid());
            break;
        }
    }
    return 0;
}

此处只是多进程创建 但是 注意 进程 谁创建就要由谁回收 所以 多进程创建就牵扯多进程回收

进程状态

三状态:

就绪态 执行态 等待态

在这里插入图片描述

五状态:

新建态、终止态、运行态、 就绪态、阻塞态

在这里插入图片描述

注意:

​ 阻塞态在其他语言中进行细分又可以分为阻塞态与休眠态

​ 休眠态又可以分为有限期休眠与无限期休眠

​ 休眠状态下不会抢夺CPU 执行权

进程资源的回收

概述:

因为子进程创建的时候 共享(也就是拷贝)父进程

在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,
这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等);

回收原则:谁创建谁回收(父进程 回收 子进程资源);

1 wait函数:

作用:回收进程资源

头文件

  • #include <sys/types.h>
  • #include <sys/wait.h>

语法:

pid_t wait(int *status);      //阻塞

功能:

#include <sys/types.h>
#include <wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
     // 创建子进程
     int pid = fork();
     if (pid == 0) // 子进程中
     {
           for (int i = 0; i < 5; i++)
           {
               // 休眠一秒
               sleep(1);
               printf("子进程%d 已经休眠了%d秒\n", getpid(), i + 1);
           }
     }
     else if (pid > 0) // 父进程
     {
           // 依据回收原则
           // wait函数回收子进程
           // wait函数用于回收子进程,但是会阻塞当前进程,直到有需要回收子进程为止
           // 如果当前进程中所有的子进程都以被回收,该函数不会阻塞当前进程,会返回-1
           int s = 0;
           int id = wait(&s); // 函数传入的参数是指针 所以是 &
           printf("当前进程%d回收了进程%d\n", getpid(), id);
           printf("状态码是:%d\n", s);
           // 状态码使用的少
     }
     return 0;
}

在这里插入图片描述

2 exit 与 _exit()

区别:

  • 1,exit函数属于库函数
  • 2,_exit函数属于系统调用 【效率高】
exit函数
所需头文件
	#include <stdlib.h>
函数:
	void exit(int status);
参数:
	退出状态,0,正常退出,非0异常退出
_exit函数【*】
所需头文件
	#include <unistd.h>
函数:
	void _exit(int status);
参数:
	退出状态,0,正常退出,非0异常退出
> 注意 退出状态码取值区间   0~255 

示例:

在子进程 计算 10 内数之和,在主进程打印结果

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

//   > 注意 退出状态码取值区间   0~255
// 在子进程 计算 10 内数之和,在主进程打印结果

// 退出当前进程   库函数exit()   系统调用_exit()
int main(int argc, char const *argv[])
{ 
    int pid = fork(); // 创建子线程
    if (pid == 0)
    {
        int sum = (0 + 10) * 11 / 2;
        sleep(2);
        _exit(sum); // 将sum作为其状态码
    }
    else if (pid > 0) // 父进程
    { 
        int s = 0;
        int pid = wait(&s);    //使用了  wait   回收  进程  但是会阻塞当前进程  一直到 子进程  被回收
        printf("s = %d\n", s);
        printf("退出时状态 %d\n", WIFEXITED(s));
        printf("退出时状态 码 %d\n", WEXITSTATUS(s));
    }
    return 0;
}

在这里插入图片描述

3 WIFEXITED(status)与WEXITSTATUS(status)

作用: 从状态码中获取信息

  • 获取进程退出时状态 WIFEXITED(status)
  • 获取进程退出时的状态码 WEXITSTATUS(status)
WIFEXITED:判断进程是否正常退出
	取出子进程的退出信息 WIFEXITED(status)如果子进程是正常终止的,取出的字段值非零。
WEXITSTATUS(status):返回子进程的退出状态值
	退出状态值保存在status变量的8~16 位。

语法:

WIFEXITED(status)

WEXITSTATUS(status)

4 waitpid函数 【*】

 > wait函数用于回收子进程,但是会阻塞当前进程,直到有需要回收子进程为止


 >  waitpid 优势 就是不阻塞主进程 多进程创建的回收

作用: 回收进程

语法:

头文件
	#include <sys/types.h>
	#include <sys/wait.h>
语法
	pid_t waitpid(pid_t pid, int *status, int options);
功能:
	等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。
参数:
	1 pid : 【基本只用到 -1】
			参数 pid 的值有以下几种类型:
		pid > 0 等待进程 ID 等于 pid 的子进程。
		pid = 0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会等待它。
		pid = -1 等待任一子进程,此时 waitpid 和 wait 作用一样。
		pid < -1 等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid的绝对值。
	2 status : 【基本是NULL】
			进程退出时的状态信息。和 wait() 用法一样。
	3 options : 【基本只用 wnohang】
			options 提供了一些额外的选项来控制 waitpid()0:同 wait(),阻塞父进程,等待子进程退出。
					WNOHANG:没有任何已经结束的子进程,则立即返回。(非阻塞)
					WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(由于涉及到一些跟踪调试方面的知识,加之极少用到)
返回值:
		1)当正常返回的时候,waitpid() 返回收集到的已经回收子进程的进程号;
		2)如果设置了选项 WNOHANG,而调用中 waitpid() 还有子进程在运行,且没有子进程退出,返回0, 父进程的所有子进程都已经退出了返回-1; 返回>0表示等到一个子进程退出;(重要)
		3)如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在,
	如:当pid所对应的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid()就会出错返回,这时errno被设置为ECHILD

多进程回收

弥补上面只创建了多进程而没有进行回收的问题

【模板】

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

// 多线程创建 并回收 

int main(int argc, char const *argv[])
{
    // 多进程的创建 i就是 几个线程的创建 else if 就增加到i
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        int pid = fork();
        if (pid == 0)
        {
            printf("进程%d被创建了\n",getpid());
            break;
        }
    }
    if (i == 0)
    {
        printf("i = %d,当前进程号:%d\n", i, getpid());
    }
    else if (i == 1)
    {
        printf("i = %d,当前进程号:%d\n", i, getpid());
    }
    else if (i == 2)
    {
        printf("i = %d,当前进程号:%d\n", i, getpid());
    }
    else  if (i == 3) // 能走到这 就只能是父进程了
    {
        printf("i = %d,父进程\n",i);
        // 注意使用非阻塞的 waitpid() 要用死循环
        //  waitpid(-1,NULL,WNOHANG) 此函数回收进程时  一参基本就是-1  二参只有不要状态码就是null  基本就用的时三参
        while (1)
        {
            int pid = waitpid(-1, NULL, WNOHANG);
            if (pid == -1)
            {
                printf("所有子进程都已经回收\n");
                break;
            }
            // else if (pid > 0)
            // {
            //     printf("子进程%d被回收\n", pid);
            // }
        }
    }
    return 0;
}

在这里插入图片描述

5 atexit函数

作用:进程在退出前可以用 atexit 函数注册退出处理函数

> 注意:
		1,一个进程可以登记多至 32 个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序,用atexit函数来登记这些函数。
		2, 以登记这些函数的相反顺序调用它们。同一函数如若登记多次,则也被调用多次
		3, 执行注册的函数  还是子进程

语法:

int atexit (void (*_func) (void))

示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>
#include <stdlib.h>
void fun01()
{
    printf("函数1\n,当前进程是%d\n",getpid());
}
void fun02()
{
    printf("函数2\n");
}
void fun03()
{
    printf("函数3\n");
}

int main(int argc, char const *argv[])
{
    int pid = fork();
    if (pid == 0)
    {
        // 注册退出处理函数
        atexit(fun01);
        atexit(fun02);
        atexit(fun03);
        atexit(fun01);
        printf("当前 子 进程%d end\n", getpid());
    }
    else if (pid > 0)
    {
        wait(NULL); // 状态码不要
        printf("当前 父 进程%d end\n", getpid());
    }

    return 0;
}

在这里插入图片描述

特殊进程

僵尸进程

> 子进程退出,父进程没有回收子进程资源,子进程为僵尸进程   (有危害)
> 子进程的PID被占用,系统的PID是有数量限制。

孤儿进程

>  父进程先结束,子进程为孤儿进程.                     (无害的)  
>  孤儿进程被1号进程接管(当孤儿进程结束时,1号进程负责回收其资源

守护进程

> 概念:又名精灵进程,是脱离终端的孤儿进程。

进程补充:

1 终端

终端:是与计算机系统相连的一种输入输出设备

> 在UNIx系统中,用户通过终端登录系统后得到一个shell进程,这个终端成为shell进程的控制终端(ControllingTerminal),进程中,控制终端是保存在PCB中的信息,而fork会复制PCB中的信息,因此由Shel1进程启动的其它进程的控制终端也是这个终端。

在这里插入图片描述

> 	一个终端同时只能被一个进程池操作
	当shell进程启动a.out后,将终端的控制权交给了a.out,而shell进程失
	去了终端控制权,此时在终端中输入内容,是被a.out接收.
	当a.out进程结束,会将终端控制权交还给shell进程
	如果父进程先与子进程结束,子进程此时不能输入,只能输出

函数:

作用:
	获取当前进程所属终端名称
			#include <unistd.h>
			char *ttyname(int fd);
	功能:由文件描述符查出对应的文件名
		参数:fd:文件描述符
		返回值:
				成功:终端名
				失败:NULL

示例:证明是同一终端【其实就是底部黑框】

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>//exit
int main(int argc, char *argv[])
{
    pid_t pid = fork();
    if(pid == 0)//子进程
    {
        sleep(2);
        int num = 0;
        scanf("%d", &num);
        printf("子进程%u中的num=%d,所属终端名:%s\n", getpid(), num, ttyname(0));
    }
    else if(pid > 0)//父进程
    {
        int num = 0;
        scanf("%d", &num);
        printf("父进程%u中的num=%d,所属终端名:%s\n", getpid(), num, ttyname(0));
    }

    return 0;
}

发现 终端名都是一样的 都是 dev/pts/25

在这里插入图片描述

2 进程组

概念:

>	代表一个或多个进程的集合。
	每个进程都有对应的进程组。
	进程组ID为当前进程中第一进程的ID。
	如果一个进程的ID和组ID相同那么这个进程就是组长进程。
	当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。
	shel1进程启动的进程独立为一个进程组
	如果进程中只是组长进程结束,当前进程组不会解散.只有进程组的所有进程离开(终止或转移),该进程组才会解散。
	一个进程可以为自己或子进程设置进程组ID。
		注意:组长进程不能设置进程组id

			如果进程 ID==进程组 ID==会话ID,那么该进程为会话首进程(会长)。

示例:

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
    int xxz_pid = fork(); // 创建子进程
    if (xxz_pid == 0)
    {    
        printf("当前子进程id:%d\t所属进程组id:%d\n",getpid(),getpgrp());//getpgrp():获取当前进程所在的进程组id
    }
    else if ( xxz_pid > 0)
    {
       printf("当前父进程id:%d\t所属进程组id:%d\n",getpid(),getpgrp());
    }
    return 0;
}

在这里插入图片描述

设置当前进程或子进程所在的进程组

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
    int xxz_pid = fork(); // 创建子进程
    if (xxz_pid == 0)
    {   
        //设置当前进程或子进程所在的进程组
        setpgid(getpid(),getpid()); 
        printf("当前子进程id:%d\t所属进程组id:%d\n",getpid(),getpgrp());  //getpgrp():获取当前进程所在的进程组id
    }
    else if ( xxz_pid > 0)
    {
       printf("当前父进程id:%d\t所属进程组id:%d\n",getpid(),getpgrp());
    }
    return 0;
}

a

3 会话

概念:

>	
	会话是一个或多个进程组的集合。
	一个会话可以有一个控制终端。这通常是终端设备或伪终端设备;建立与控制终端连接的会话首进程被称为控制进程;
	一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组;
	如果一个会话有一个控制终端,则它有一个前台进程组,其它进程组为后台进程组;
	如果终端接口检测到断开连接,则将挂断信号发送至控制进程(会话首进程)

函数getsid

> 作用:获取会话id
	所需头文件
			#include <unistd.h>
	函数
			pid_t getsid(pid_t pid);
	参数:
			pid:进程号,pid 为 0 表示查看当前进程 session ID(会话id)
	返回值:
			成功:返回调用进程的会话 ID
			失败:-1
组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。

函数setsid

>
	作用:创建会话
				所需头文件
						#include <unistd.h>
				函数:
					pid_t setsid(void);
				功能:
					创建一个会话,并以自己的 ID 设置进程组 ID,同时也是新会话的 ID。调用了setsid 函数的进程,既是新的会长,也是新的组长。
				参数:
						无
				返回值:
						成功:返回调用进程的会话 ID
						失败:-1
注意实现:
1,组长进程不能设置为会话
2,需有root权限(ubuntu 不需要)
3,新会话丢弃原有的控制终端,该会话没有控制终端

示例1:

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
#include <stdlib.h>
//     会话
int main(int argc, char const *argv[])
{
     int xxz_pid = fork();
     if (xxz_pid == 0)
     {
           setpgid(getpid(), getpid());
           printf("当前子进程id:%d\t所属进程组id:%d\t所属会话id:%d\n", getpid(), getpgrp(), getsid(getpid()));
     }
     else if (xxz_pid > 0)
     {
           printf("当前父进程id:%d\t所属进程组id:%d\t所属会话id:%d\n", getpid(), getpgrp(), getsid(getpid()));
     }
     return 0;
}

在这里插入图片描述

示例2: 创建新会话

注意 组长id 不能设置为会话

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
#include <stdlib.h>
//     创建新会话  
int main(int argc, char const *argv[])
{
 int xxz_pid = fork();
 if (xxz_pid == 0)
 {
     // 设置当前进程或子进程所在的进程组
     // setpgid(getpid(), getpid());
     // 设置当前进程为一个新的会话
     // 组长进程不能设为新的会话
     setsid();
     printf("当前子进程id:%d\t所属进程组id:%d\t所属会话id:%d\n", getpid(), getpgrp(), getsid(getpid()));
 }
 else if (xxz_pid > 0)
 {
     printf("当前父进程id:%d\t所属进程组id:%d\t所属会话id:%d\n", getpid(), getpgrp(), getsid(getpid()));
 }
 return 0;
}

在这里插入图片描述

4 守护进程

  • 1 忽略SIGHUP(信号)防止被终端误杀
  • 2 创建子进程,父进程退出(必须) 所有工作在子进程中进行形式上脱离了控制终端
  • 3 在子进程中创建新会话(必须) setsid()函数使子进程完全独立出来,脱离控制
  • 4 改变当前目录为根目录(不是必须) chdir()函数 防止占用可卸载的文件系统 也可以换成其它路径
  • 5 重设文件权限掩码(不是必须)umask()函数 防止继承的文件创建屏蔽字拒绝某些权限 增加守护进程灵活性
  • 6 关闭文件描述符(不是必须) 继承的打开文件不会用到,浪费系统资源,无法卸载
  • 7 开始执行守护进程核心工作(必须) 守护进程退出处理程序模型

vfork

函数:

pid_t vfork(void);

作用:

vfork 函数和fork 函数一样都是在已有的进程中创建一个新的进程,但它们创建的子进程是有区别的

vfork保证子进程先运行,在它调用exec或exit之后,父进程才可能被调度运行。

vfork 和fork一样都创建一个子进程,但它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用

exec(或exit),于是也就不访问该地址空间。

exec函数簇 【*】

建议:

  • 调用系统的 使用 execlp ()
  • 调用别人的 用 execl ()

语法:

int execl(const char *path, const char *arg, .../* (char  *) NULL */);
int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, 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函数

exec 后字母的含义

>	l:表示exe函数族的参数是通过列表(list),传递。
	v:表示exe函数族的参数是通过指针数组(vector),传递。
	p:p表示通过环境变量查找命令(程序)
	e:可以使用系统环境变量。
注意:
	exec 函数族的 最后一个 参数为NULL

示例:

#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    printf("执行exec前\n");
    // 此时我们发现,该函数后的代码没有被执行
    // 因为exec启动了别人的程序,顾执行的是启动的程序中的代码
    // 将不在执行当前进程中的代码
    // execl("/usr/games/sl","./sl",NULL);
    // execl("/bin/ls","./ls","-l","-a",NULL);
    //  execl("/bin/ls","./ls","-la",NULL);
    // p:在环境变量path中寻找
    //  execlp("ls","./ls","-la",NULL);
    // 此时t.out不在环境变量中,顾找不到
    //  execlp("t.out","./t.out",NULL);
    //  execl("./t.out","./t.out",NULL);
    char *tem[] = {"./ls", "-l", "-a", NULL};
    execv("/bin/ls", tem);
    printf("执行exec后\n");
    return 0;
}

exec函数族与vfork结合

优势:

  • 1,节省内存
  • 2,可以与父进程交替执行

示例1

#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    for (int i = 0; i < 3; i++)
    {
        sleep(1);
        printf("test i = %d\n", i);
    }
    return 0;
}

目的:

将03_test.c编写生成可执行文件 t

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>
int main(int argc, char const *argv[])
{
	int pid = vfork();
	if(pid == 0)
	{
		execl("./t","./t",NULL);
	}
	else if(pid > 0)
	{
		for (int i = 0; i < 3; i++)
		{
			sleep(1);
			printf("code i = %d\n",i);
		}
			wait(NULL);
	}
	return 0;
}

system

作用

system 会调用 fork 函数产生子进程,子进程调用 exec 启动要启动的其他程序

语法

	#include <stdlib.h>
		int system(const char *command);
参数:
	要执行的命令的字符串。
返回值:
	如果command 为 NULL,则system()函数返回非 O,一般为 1。
	如果 system()在调用/bin/sh 时失败则返回127,其它失败原因返回-1

示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>
int main(int argc, char const *argv[])
{
	printf("system执行前\n");
	int code = system("./my.sh");
	printf("%d\n",WIFEXITED(code));
	printf("system执行后\n");
	return 0;
}	

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2188450.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于vue框架的大学生勤工俭学咨询服务系统的设计与实现60uw9(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;大学生,企业,招聘信息,在线咨询,咨询回复,职位应聘 开题报告内容 基于Vue框架的大学生勤工俭学咨询服务系统的设计与实现 开题报告 一、研究背景 随着高等教育的普及与就业市场的竞争加剧&#xff0c;大学生勤工俭学已成为一种普遍现…

<<机器学习实战>>1-9节笔记

2.前言与导学 从关注算法的分类与特性到关注算法适合解决哪类问题 很多经典算法不再有效&#xff0c;但特征工程、集成学习越来越有效&#xff0c;和深度学习分别适合于不同领域 3、基本概念 如果预测目标是离散的&#xff0c;则是分类问题&#xff0c;否则回归 机器学习相比…

【AIGC】ChatGPT开发者必备:如何获取 OpenAI 的 API Key

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;API Key的重要性&#x1f4af;获取API Key的基本步骤&#x1f4af;定价策略和使用建议&#x1f4af;小结 &#x1f4af;前言 在现代应用开发中&#xff0c;获取OpenAI的…

TCP Analysis Flags 之 TCP ZeroWindowProbe

前言 默认情况下&#xff0c;Wireshark 的 TCP 解析器会跟踪每个 TCP 会话的状态&#xff0c;并在检测到问题或潜在问题时提供额外的信息。在第一次打开捕获文件时&#xff0c;会对每个 TCP 数据包进行一次分析&#xff0c;数据包按照它们在数据包列表中出现的顺序进行处理。可…

什么是沉默成本?超详细+通俗易懂版

沉默成本是一个在会计学、金融学以及经济学中常用的概念&#xff0c;但更常见的表述是沉没成本&#xff08;Sunk Cost&#xff09;。沉没成本指的是已经发生且无法收回的成本&#xff0c;这些成本与当前的决策无关&#xff0c;但往往会影响人们的决策过程。以下是对沉没成本的详…

【MySQL】Ubuntu环境下MySQL的安装与卸载

目录 1.MYSQL的安装 2.MYSQL的卸载 1.MYSQL的安装 首先我们要看看我们环境里面有没有已经安装好的MySQL 我们发现是默认是没有的。 我们还可以通过下面这个命令来确认有没有mysql的安装包 首先我们得知道我们当前的系统版本是什么 lsb_release -a 我们在找apt源的时候&a…

vulnhub-unknowndevice64 2靶机

vulnhub&#xff1a;https://www.vulnhub.com/entry/unknowndevice64-2,297/ 导入靶机&#xff0c;放在kali同网段&#xff0c;扫描 靶机在192.168.81.9&#xff0c;扫描端口 啥啊这都是&#xff0c;详细扫描一下 5555是adb&#xff0c;6465是ssh&#xff0c;12345看样子应该是…

Python 工具库每日推荐 【BeautifulSoup】

文章目录 引言Python工具库的重要性今日推荐:BeautifulSoup工具库主要功能:使用场景:安装与配置快速上手示例代码代码解释实际应用案例获取 BeautifulSoup 官网文档首页的标题与所有图片案例分析扩展阅读与资源优缺点分析优点:缺点:总结【 已更新完 TypeScript 设计模式 专栏…

医疗应急三维电子沙盘系统

一、主要硬件功能指标要求&#xff1a; 1.沙盘尺寸≥98寸&#xff1b;分辨率&#xff1a;≥19201080&#xff1b;亮度500cd/m2&#xff1b;对比度4000&#xff1a;1&#xff1b;显示模式16&#xff1a;9&#xff1b;电源&#xff1a;100VAC&#xff5e;240VAC(50/60Hz)&#xf…

如何在 MySQL 中处理大量的 DELETE 操作

在 MySQL 数据库的使用过程中&#xff0c;我们有时会面临需要处理大量 DELETE 操作的情况。如果处理不当&#xff0c;可能会导致数据库性能下降、锁等待甚至系统崩溃。本文将介绍一些在 MySQL 中处理大量 DELETE 操作的方法。 一、问题背景 当需要删除大量数据时&#xff0c;…

手写mybatis之实现映射器的注册和使用

前言 其实对于解决这类复杂的项目问题&#xff0c;核心在于要将主干问题点缩小&#xff0c;具体的手段包括&#xff1a;分治、抽象和知识。运用设计模式和设计原则等相关知识&#xff0c;把问题空间合理切割为若干子问题&#xff0c;问题越小也就越容易理解和处理。就像你可以把…

【论文阅读】Segment Anything Model for Road Network Graph Extraction

【论文阅读】Segment Anything Model for Road Network Graph Extraction (CVPRW 2024) Paper链接&#xff1a;https://openaccess.thecvf.com/content/CVPR2024W/SG2RL/html/Hetang_Segment_Anything_Model_for_Road_Network_Graph_Extraction_CVPRW_2024_paper.html 文章目录…

基于SSM医疗信息管理系统(源码+定制+参考)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

vue文件的认识

1.package.json 项目包文件 里面包含许多可以在命令提示符中运行的命令&#xff0c;这里使用vue3创建的项目&#xff0c;所以dev是“vite”。 2.main.js 整个项目的入口文件&#xff0c;createApp创建应用实例 createApp(App).mount(#app) 这句话意思是以App作为参数生成一…

ElasticSearch备考 -- Search scroll

一、题目 Search for all documents in all indices As above, but use the scroll API to return the first 100 results while keeping the search context alive for 2 minutes Use the scroll id included in the response to the previous query and retrieve the next ba…

启用vnc访问Dell 服务器IDRAC 7虚拟控制台

Dell IDRAC 7 版本太老&#xff0c;SSL证书过期&#xff0c;IDRAC的Java和本地远程虚拟机控制台访问不了&#xff0c;怎么办&#xff1f; 可以启用vnc访问IDRAC 虚拟控制台

Vue2如何在网页实现文字的逐个显现

目录 Blue留言&#xff1a; 效果图&#xff1a; 实现思路&#xff1a; 代码&#xff1a; 1、空字符串与需渲染的字符串的定义 2、vue的插值表达式 3、函数 4、mounted()函数调用 结语&#xff1a; Blue留言&#xff1a; 在国庆前夕&#xff0c;突发奇想&#xff0c;我想…

SAP HCM 自定义累计期间

需求是按3月&#xff5e;8月&#xff0c;9月&#xff5e;次年2月&#xff0c;做为累计期间&#xff0c;怎么处理&#xff1f;

微信小程序hbuilderx+uniapp+Android 新农村综合风貌旅游展示平台

目录 项目介绍支持以下技术栈&#xff1a;具体实现截图HBuilderXuniappmysql数据库与主流编程语言java类核心代码部分展示登录的业务流程的顺序是&#xff1a;数据库设计性能分析操作可行性技术可行性系统安全性数据完整性软件测试详细视频演示源码获取方式 项目介绍 小程序端…

5G NR coreset 简介

文章目录 5G 为何引入CORESETCORESET介绍CORESET 分类 5G 为何引入CORESET 在LTE系统中&#xff0c;PDCCH频域占据整个带宽&#xff0c;始于占据每个RB的前1~3个OFDM 符号&#xff0c;这种情况下&#xff0c;UE 只需知道PDCCH 所占据的OFDM 符号数&#xff0c;就可以确定PDCCH…