当前位置: 首页 > news >正文

Linux高级系统编程-3 进程

概念

进程与程序的区别

        程序:一个可执行文件, 占磁盘空间,是静态的
        进程:一个程序运行的过程, 占内存,动态的。

单道程序和多道程序

        单道程序设计: 所有进程一个一个排队执行。若 A 阻塞, B 只能等待,即使 CPU 处于 空闲状态。已弃用
        多道程序设计: 在计算机内存中同时存放几道相互独立的 程序,它们在管理程序控制之 下,相互穿插的运行。(并行和并发)

并行和并发

并行 (parallel):
        (多核状态下 ) 指在同一时刻 , 有多条指令在多个处理器上同时执行。
并发 :
        (单核,宏观上的并行 ), 多条指令快速轮转执行 , 达到宏观上的并行 , 微观上是顺序执行。

进程块控制(PCB)

        进程运行时, 内核为进程每个进程分配一个 PCB (进程控制块) , 维护进程相关的信 息,Linux 内核的进程控制块是 task_struct 结构体
task_struct结构体:
        在 /usr/src/linux-headers-xxx/include/linux/sched.h 文件中可以查看 task_struct 结构体定义其内部成员有很多,我们掌握以下部分即可:
        进程id:c 语言使用 pid_t 的类型表示 , 其实就是一个非负整数
        进程的状态: 有就绪、运行、挂起、停止等状态。
        等
PCB存储位置:
在内核中

进程号

概念:

        每个进程都由一个进程号来标识,其类型为 pid_t(整型),进程号的范围:0~ 32767。进程号总是唯一的,但进程号可以重用。当一个进程终止后,其进程号就可以 再次使用

        PID:进程号
        PPID:父进程号
        PGID:进程组号

获取进程id

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
功能:获取本进程号( PID
参数:无
返回值:本进程号(失败 -1

获取进程的父进程id

#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
功能:获取调用此函数的进程的父进程号( PPID
参数:无
返回值:调用此函数的进程的父进程号( PPID

获取进程所在进程组id

#include <sys/types.h>
#include <unistd.h>
pid_t getpgid(pid_t pid);
功能:获取进程组号( PGID
参数: pid :进程号
返回值:参数为 0 时返回当前进程组号,否则返回参数指定的进程的进程组号
注意 : 进程组 id 就是该进程组内第一个进程的 id

创建进程fork()

概述

父子进程,系统允许一个进程可以创建新进程,该进程即为新进程的父进程,新进程即为子进程

函数

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父
进程。
参数:无
返回值:
成功:子进程中返回 0 ,父进程中返回子进程 ID pid_t ,为整型。
失败:返回 -1
注意:
        foek失败的两个主要原因是:
        1)当前的进程数已经达到了系统规定的上限,这时 errno 的值被设置为 EAGAIN。
        2)系统内存不足,这时 errno 的值被设置为 ENOM
注意 :
        子进程会从fork 函数后开始执行
示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{printf("啦啦啦\n");
/**
* fork():创建一个进程
* 参数:无
* 返回值:
* 父进程中返回子进程的id
* 子进程中返回0
* -1表示创建失败
* 注意:子进程执行是从fork后开始执行
*/int id = fork();if (id < 0){printf("创建失败id:%d\n",id);}else if(id > 0){printf("父进程id:%d\n,创建的子进程id:%d\n",getpid(),id);}else if (id == 0){printf("父进程id:%d\n,创建的子进程id:%d\n,创建的id:%d\n",getppid(),getpid(),id);}printf("德玛西亚\n");while(1);return 0;
}

父子进程关系

        使用 fork 函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间。
        地址空间: 包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。
        子进程所独有的只有它的进程号,计时器等。
        因此,使用 fork 函数的代价是很大的。
示例
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{printf("啦啦啦\n");int id = fork();printf("德玛西亚\n");while(1);return 0;
}

结果:

啦啦啦

德玛西亚

德玛西亚

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{printf("啦啦啦");int id = fork();printf("德玛西亚\n");while(1);return 0;
}

结果:

啦啦啦德玛西亚
啦啦啦德玛西亚
分析:
        printf()在打印内存时会将打印的数据放在缓冲区
        子进程将父进程的缓冲区也复制了一份
        代码1 printf() 输出时带有 \n, 触发了行刷新 ( 刷新状态 : 行刷新 , 关闭刷新 , 满刷新 , 强 制刷新), 已经将缓冲区中的内容刷新出并打印 , 此时缓冲区中没有内容 , 子进程拷贝到的 缓冲区数据是空.
所以父进程打印
        啦啦啦
        德玛西亚
子进程打印
        德玛西亚
代码 2 printf() 输出时没带有 \n, 无法触发了刷新 ( 刷新状态 : 行刷新 , 关闭刷新 , 满刷新, 强制刷新 ), 此时缓冲区中有内容 , 啦啦啦 , 子进程拷贝到的缓冲区数据也有啦啦啦 .
所以父进程打印
        啦啦啦德玛西亚
子进程打印
        啦啦啦, 德玛西亚
注意 : 库函数有缓存区 , 系统调用没有缓存区
思考一下,以下代码的执行结果
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(int argc, char *argv[])
{//printf是库函数有缓冲区 hello world先放入缓冲区 被子进程复制了一份printf("hello world1");//write是系统调用 直接将字符串 写入1号文件 没有缓冲区 不被子进程复制//0,输入//1,输出//2,错误输出write(1,"hello world2",12);//创建子进程pid_t pid = fork();return 0;
}

结果:hello world1hello world2hello world1

进程状态

分类
可以分为三大状态与五大状态
        三大状态
                运行态,就绪态,阻塞态
        五大状态
                新建态、终止态,运行态,就绪态,阻塞态

ps查看进程状态

ps 命令
作用 : 查看
参数 :
        -a 显示终端上的所有进程,包括其他用户的进程
        -u 显示进程的详细状态
        -x 显示没有控制终端的进程
        -w 显示加宽,以便显示更多的信息
        -r 只显示正在运行的进程
        ps -aux:显示当前用户正在运行的进程信息
        ps -ajx:显示正在运行的相关联进程信息 ( 包含父进程 id(ppid), id(pdid))
        显示信息中STAT 参数含义
        D 不可中断 Uninterruptible usually IO
        R 正在运行,或在队列中的进程
        S(大写 ) 处于休眠状态
        T 停止或被追踪
        Z 僵尸进程
        W 进入内存交换(从内核 2.6 开始无效)
        X 死掉的进程
        < 高优先级
        N 低优先级
        s 包含子进程
        + 位于前台的进程组

进程资源的回收

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

wait函数

作用 : 等待子进程运行结束
语法
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);// 阻塞
功能:
        等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收该子进程的资 源。
参数:
        status:进程退出时的状态信息。
返回值:
        成功:已经结束子进程的进程号
        失败: -1
注意 :
        1,会阻塞当前进程 , 直到回收一个子进程
        2,因为回收原则 , 谁创建谁回收 , 所以该函数在父进程中调用
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{int pid = fork();if(pid < 0){printf("创建进程失败");}else if(pid == 0){for(int i = 0; i < 10; i++){printf("子进程%u正在执行第%d次\n",getpid(),i);/*sleep:休眠参数休眠时间liunx下参数单位秒,windows下单位毫秒*/sleep(1);}}else if(pid > 0){printf("父进程正在等待子进程执行完毕\n");wait(NULL);printf("父进程已经回收了子进程%u\n",pid);}return 0;
}

exit函数与_exit函数

exit 函数 : 库函数
所需头文件
        #include <stdlib.h>
函数 :
        void exit(int status);
参数 :
        退出状态,0, 正常退出 , 0 异常退出
_exit 函数 : 系统调用
所需头文件
        #include <unistd.h>
函数 :
        void _exit(int status);
参数 :
        退出状态,0, 正常退出 , 0 异常退出
exit _exit 的区别
        exit:底层调用系统调用函数的 _exit 函数 , 所以相对与 _exit 而言效率低
        _exit:系统调用函数
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{int pid = fork();if(pid < 0){printf("创建进程失败");}else if(pid == 0){for(int i = 0; i < 10; i++){printf("子进程%u正在执行第%d次\n",getpid(),i);/*sleep:休眠参数休眠时间liunx下参数单位秒,windows下单位毫秒*/sleep(1);}//进程退出//参数0正常退出,非0异常退出,参数就是子进程退出状态值//exit(-1);//库函数,底层封装_exit,效率低_exit(-1);//系统调用函数,效率高printf("Hello");//因为子进程已经退出,所以此代码不会执行}else if(pid > 0){printf("父进程正在等待子进程执行完毕\n");wait(NULL);printf("父进程已经回收了子进程%u\n",pid);}return 0;
}

WIFEXITED(status)WEXITSTATUS(status)

WIFEXITED: 判断进程是否正常退出
        取出子进程的退出信息 WIFEXITED(status) 如果子进程是正常终止的,取出的字段值非零。
WEXITSTATUS(status): 返回子进程的退出状态值
        退出状态值保存在status变量的 8~16 位。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{int pid = fork();if(pid < 0){printf("创建进程失败");}else if(pid == 0){for(int i = 0; i < 3; i++){printf("子进程%u正在执行第%d次\n",getpid(),i);/*sleep:休眠参数休眠时间liunx下参数单位秒,windows下单位毫秒*/sleep(1);}//进程退出//参数就是子进程退出状态值//exit(100);//库函数,底层封装_exit,效率低_exit(100);//系统调用函数,效率高printf("Hello");//因为子进程已经退出,所以此代码不会执行}else if(pid > 0){printf("父进程正在等待子进程执行完毕\n");int status = 0;pid_t ret = wait(&status);printf("子进程%u,是否正常退出(WIFEXITED):%d\t,退出状态值(WEXITSTATUS)为:%d\n",ret,WIFEXITED(status),WEXITSTATUS(status));printf("父进程已经回收了子进程%u\n",pid);}return 0;
}

waitpid函数

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
功能 :
        等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。
参数:
        pid : 参数 pid 的值有以下几种类型:
        pid > 0 等待进程 ID 等于 pid 的子进程。
        pid = 0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程 组,waitpid 不会等待它。
        pid = -1 等待任一子进程,此时 waitpid wait 作用一样。
        pid < -1 等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的 绝对值。
        status : 进程退出时的状态信息。和 wait() 用法一样。
        options : 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<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){perror("fork\n");}else if(pid == 0)//子进程{int i=0;for(i=10;i>0;i--){printf("子进程%u剩余的生命%d\n", getpid(), i);sleep(1);}//进程退出//exit(-1);//库函数 底层调用的系统调用_exit_exit(10);//系统调用函数 10就是子进程退出的状态值}else if(pid > 0)//父进程{int status = 0;printf("父进程%u 等待子进程%u的结束\n",getpid(),pid );pid_t ret = waitpid(-1, &status, 0);//不关心状态 直接实参为NULLif(WIFEXITED(status))//子进程正常退出{//取出状态值printf("子进程%u已经结束 状态值为:%d\n", ret,WEXITSTATUS(status));}}return 0;
}

atexit 函数

作用 : 进程在退出前可以用 atexit 函数注册退出处理函数
注意 :
        1,一个进程可以登记多至 32 个函数,这些函数将由 exit 自动调用。我们称这些函 数为终止处理程序, atexit 函数来登记这些函数。
        2,以登记这些函数的相反顺序调用它们。同一函数如若登记多次 , 则也被调用多次
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
void fun()
{printf("进程退出\n");
}
int main(int argc, char const *argv[])
{//atexit()int p_id = fork();if(p_id == 0){atexit(fun);//子进程printf("子进程已开启\n");sleep(1);printf("子进程退出\n");exit(10);}else if(p_id > 0){//主进程wait(NULL);sleep(5);printf("父进程结束\n");}return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
void fun01()
{printf("函数1\n");
}
void fun02()
{printf("函数2\n");
}
void fun03()
{printf("函数3\n");
}
int main(int argc, char const *argv[])
{atexit(fun01);atexit(fun02);atexit(fun03);atexit(fun01);sleep(3);return 0;
}

特殊进程

僵尸进程

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

孤儿进程

        父进程先结束,子进程为孤儿进程.( 无害的 )
        孤儿进程被1 号进程接管(当孤儿进程结束时 ,1 号进程负责回收其资源)。
#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){perror("fork\n");}else if(pid == 0)//子进程{printf("子进程%u\n", getpid());while(1);_exit(-1);}else if(pid > 0)//父进程{}return 0;
}

守护进程

        也被称为为精灵进程、后台进程,
        是一种只在运行于相对干净环境、不受终端影响的、常驻内存的进程,就像神话中 的精灵拥有不死的特性,长期稳定提供某种功能或服务。
        在Unix/Linux 系统中,使用 ps 命令可以看到许多以 -d 结尾的进程,它们大多 都是守护进程。
        有许多程序或服务理应成为这种“ 不死 的守护进程,比如提供系统网络服务的核心 程序systemd-networkd ,只要系统需要基于 TCP/IP 协议栈进行网络通信,它就应该一 直常驻内存,永不退出。

创建守护进程步骤

1 忽略 SIGHUP( 信号 ) 防止被终端误杀
2 创建子进程,父进程退出 ( 必须 ) 所有工作在子进程中进行形式上脱离了控制终端
3 在子进程中创建新会话 ( 必须 ) setsid() 函数使子进程完全独立出来,脱离控制
4 改变当前目录为根目录 ( 不是必须 ) chdir() 函数 防止占用可卸载的文件系统 也可以换成其它路径
5 重设文件权限掩码 ( 不是必须 )umask() 函数 防止继承的文件创建屏蔽字拒绝某些权 限 增加守护进程灵活性
6 关闭文件描述符 ( 不是必须 ) 继承的打开文件不会用到,浪费系统资源,无法卸载
7 开始执行守护进程核心工作 ( 必须 ) 守护进程退出处理程序模型
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, char const *argv[])
{// 1,忽略挂断信号SIGHUP,防止被终端误杀//SIG_IGN:忽略指定的信号signal(SIGHUP, SIG_IGN);pid_t pid = fork();//父进程结束if (pid > 0)_exit(-1);//子进程设置会话setsid();//改变工作目录(非必须)chdir("/");//设置权限掩码umask(0002);//关闭文件描述符0 1 2close(0);close(1);close(2);//守护进程的核心任务while (1){//核心任务}return 0;
}

多进程

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{//错误演示:创建两个子进程//实际创建了3个,主线程2个,子线程一个// for (int i = 0; i < 2; i++)// {// int pid = fork();// }//正确演示:创建两个子进程//主线程创建2个,子线程不创建for (int i = 0; i < 2; i++){int pid = fork();if(pid == 0){break;}}return 0;
}

退出

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>//exit
int main(int argc, char *argv[])
{//创建3个子进程int i=0;for(i=0;i<3;i++){pid_t pid = fork();if(pid == 0)//子进程break;}if(i==0)//子进程1{//任务1的代码printf("子进程1:%u\n", getpid());sleep(5);_exit(-1);}else if(i==1)//子进程2{//任务2的代码printf("子进程2:%u\n", getpid());sleep(3);_exit(-1);}else if(i==2)//子进程3{//任务3的代码printf("子进程3:%u\n", getpid());sleep(4);_exit(-1);}else if(i == 3)//父进程{//回收子进程的资源while(1){//-1:等待任一子进程//WNOHANG:不阻塞pid_t ret = waitpid(-1, NULL, WNOHANG);if(ret > 0){printf("子进程:%u已经退出\n", ret);}else if(ret == 0){continue;//还有子进程在运行 需要继续等待}else if(ret < 0){break;//所有子进程都已经结束}}}return 0;
}

进程补充

终端

终端 : 是与计算机系统相连的一种输入输出设备
        在UNIX 系统中 , 用户通过终端登录系统后得到一个 Shell 进程 , 这个终端成为 Shell 进程 的控制终端(Controlling Terminal), 进程中 , 控制终端是保存在 PCB 中的信息 , 而 fork会复制 PCB 中的信息,因此由 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(3);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;
}

进程组

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

获取所属进程组id

所属头文件
#include <unistd.h>
函数 :
        pid_t getpgrp(void);
功能 :
        获取当前进程的进程组 ID
参数 :
        无
返回值 :
        总是返回调用者的进程组 ID
函数
        pid_t getpgid(pid_t pid);
功能:
        获取指定进程的进程组 ID
参数:
        pid:进程号,如果 pid = 0 ,那么该函数作用和 getpgrp 一样
返回值:
        成功:进程组ID
        失败:-1

设置进程组

函数 :
        int setpgid(pid_t pid, pid_t pgid)
功能:
        改变进程默认所属的进程组。通常可用来加入一个现有的进程组或创建一个新进程 组。
参数:
        将参1 对应的进程,加入参 2 对应的进程组中
返回值:
        成功:0
        失败:-1
#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();setpgid(pid,pid);if(pid == 0)//子进程{printf("子进程%d,所属组id:%d\n", getpid(), getpgrp());}else if(pid > 0)//父进程{printf("父进程%d,所属组id:%d\n", getpid(), getpgrp());}while(1);return 0;
}

会话

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

函数getsid

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

函数setid

作用 : 创建会话
所需头文件
        #include <unistd.h>
函数 :
        pid_t setsid(void);
功能:
        创建一个会话,并以自己的 ID 设置进程组 ID ,同时也是新会话的 ID 。调用了 setsid 函数的进程,既是新的会长,也是新的组长。
参数:
        无
返回值:
        成功:返回调用进程的会话 ID
        失败:-1
注意实现 :
        1,组长进程不能设置为会话
        2,需有 root 权限 (ubuntu 不需要 )
        3,新会话丢弃原有的控制终端,该会话没有控制终端
#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)//子进程{printf("子进程1id:%d\t,所属组id:%d\t,会话id:%d\n",getpid(),getpgrp(),getsid(getpid()));sleep(2);setsid();printf("子进程2id:%d\t,所属组id:%d\t,会话id:%d\n",getpid(),getpgrp(),getsid(getpid()));while(1);}else if(pid > 0)//父进程{printf("主进程id:%d\t,所属组id:%d\t,会话id:%d\n",getpid(),getpgrp(),getsid(getpid()));while(1);}return 0;
}

vfork函数

作用 : 创建一个进程
vfork fork 的区别
        vfork 保证子进程先运行,在它调用 exec exit 之后,父进程才可能被调度运 行。而fork 父子进程同时执行。
        子进程在调用exec exit 前与父进程共享空间
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main(int argc, char *argv[])
{int num = 10;// pid_t pid = fork();pid_t pid = vfork();if(pid == 0)//子进程{num=100;printf("子进程num=%d\n",num);// sleep(3);_exit(-1);}else if(pid > 0)//父进程{// wait(NULL);printf("主进程num=%d\n",num);}return 0;
}

exec函数族

如果想要通过运行的进程 , 启动另一个程序 , 需要用到 exec 函数族
相关函数:
#include <unistd.h>
extern char ** environ ;
int execl ( const char * path , const char * arg , ... /*(char *) NULL*/ );
int execlp ( const char * file , cconst 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 []);
int execve ( const char * filename , char * const argv [], char * const envp []);
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[])
{//which 命令,查看命令存储路径/*execl("命令存储路径","命令名","参数1","参数2",...,NULL);*/// execl("/usr/games/sl","sl",NULL);// execl("/bin/ls","ls","-a","-l","-h",NULL);// execl("/bin/ls","ls","-alh",NULL);/*execlp:从path环境变量下查找指定名,如果没有无法运行execlp("命令名","命令名","参数1","参数2",...,NULL)env命令查看环境变量*/// execlp("ls","ls","-alh",NULL);// char *tem[] = {"/ls","-alh",NULL};// execv("/bin/ls",tem);char *tem[] = {"/ls","-alh",NULL};//execvp("ls",tem);execvpe("ls",tem);return 0;
}

exec函数和当前进程的关系

        1,exec函数族中函数被调用后会创建新的进程空间 , 新的进程空间将取代调用该函数的 进程的数据段、代码段和堆栈段
        2,一个进程调用 exec 后,除了进程 ID ,进程还保留了下列特征不变 : 父进程号 进程 组号 控制终端 根目录 当前工作目录 进进程信号屏蔽集 未处理的信号等。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{printf("exec执行前\n");//exec后的程序将不在执行execl("/bin/ls","ls","-alh",NULL);printf("exec执行后\n");return 0;
}

exec函数vfork的关系

        vfork开启进程将会与父进程共享一片空间 , 并让子进程先执行完成后 , 父进程在执行
        但是exec会创建新的进程空间 , 所在在 vfork 开启的进程中 , 执行 exec 族函数 , 此时子进 程与父进程各种用于各种的空间, 独立运行
#45_test.c文件
#include <stdio.h>
#include <unistd.h>q
int main(int argc, char const *argv[])
{for (int i = 0; i < 3; i++){printf("test:%d\n",i);sleep(1);}return 0;}
#使用gcc 45_test.c -o 45_test,将源文件编译为可执行文件
#45_code.c文件
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{int pid = vfork();if (pid < 0){printf("创建进程失败");return 0;}else if(pid == 0){//子进程中代码// for(int i = 0; i < 3; i++)// {// sleep(1);// printf("子进程:%d\n",i);// }execl("./45_test","45_test",NULL);_exit(-1);}else if(pid > 0){// 父进程中代码for(int i = 0; i < 6; i++){printf("code:%d\n",i);sleep(1);}return 0;
}
从结果中我们不难看出 ,test code 交替打印 , 互不影响

system函数

system 会调用 fork 函数产生子进程,子进程调用 exec 启动要启动的其他程序
语法:
        #include <stdlib.h>
        int system(const char *command);
参数 : 要执行的命令的字符串。
返回值 :
        如果 command NULL ,则 system() 函数返回非 0 ,一般为 1
        如果 system() 在调用 /bin/sh 时失败则返回 127 ,其它失败原因返回 -1
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{printf("system前\n");system("ls -alh");printf("system后\n");return 0;
}
exec 的区别
system 会开启新的进程执行开启的程序
exec 会使用当前进程执行开始的程序 , 当前进程将会被开启的程序替换

相关文章:

Linux高级系统编程-3 进程

概念 进程与程序的区别 程序&#xff1a;一个可执行文件, 占磁盘空间&#xff0c;是静态的 进程&#xff1a;一个程序运行的过程, 占内存&#xff0c;动态的。 单道程序和多道程序 单道程序设计: 所有进程一个一个排队执行。若 A 阻塞&#xff0c; B 只能等待&#xff0…...

ES-ELSER 如何在内网中离线导入ES官方的稀疏向量模型(国内网络环境下操作方法)

ES官方训练了稀疏向量模型&#xff0c;用来支持语义检索。&#xff08;目前该模型只支持英文&#xff09; 最好是以离线的方式安装。在线的方式&#xff0c;在国内下载也麻烦&#xff0c;下载速度也慢。还不如用离线的方式。对于一般的生产环境&#xff0c;基本上也是网络隔离的…...

Excel 使用技巧

Excel 使用技巧 注意&#xff1a; excel 中设计计算的字符尽量使用英文。 拼接两段文字&#xff08;字符串拼接&#xff09; 方法一 在需要计算的单元格上,键入 点击 A1(点击需要拼接的单元格) & C1(点击需要拼接的单元格) 举例: 姓名栏想要拼接 姓 和 名 两列点击姓名这一…...

Hadoop学习笔记(HDP)-Part.03 资源规划

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …...

一个最新国内可用的免费GPT4,Midjourney绘画网站+使用教程

一、前言 ChatGPT GPT4.0&#xff0c;Midjourney绘画&#xff0c;相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚至也可以和用户进行创作交流。 然而&#xff0c;GPT-4对普…...

深入了解Java8新特性-日期时间API之ZonedDateTime类

阅读建议 嗨&#xff0c;伙计&#xff01;刷到这篇文章咱们就是有缘人&#xff0c;在阅读这篇文章前我有一些建议&#xff1a; 本篇文章大概19000多字&#xff0c;预计阅读时间长需要10分钟以上。本篇文章的实战性、理论性较强&#xff0c;是一篇质量分数较高的技术干货文章&…...

使用Vue写一个日期选择器

在 Vue 中实现日期选择器的方法有很多&#xff0c;下面提供一个简单的实现方法。 首先&#xff0c;在需要使用日期选择器的组件中引用 Vue 和 date-fns 库&#xff0c;date-fns 库是一个轻量级的 JavaScript 时间日期工具库&#xff0c;可以方便地处理日期的格式化和计算。 &…...

19、pytest通过mark标记测试函数

官方实例 [pytest] markers slow:marks tests as slow(deselect with -m "not slow")serial# content of test_mark.py import pytestpytest.mark.slow def test_mark_function():print("test_mark_function was invoked")assert 0解读与实操 通过使用p…...

Linux环境变量与命令行参数

Linux环境变量与命令行参数 一.命令行参数1.语法2.应用1:简易计算器 二.环境变量1.环境变量的概念2.环境变量的作用3.进一步理解环境变量的作用4.常见环境变量5.导出环境变量(添加环境变量)6.环境变量的特性7.另一种获取环境变量的方式8.小功能:用于身份验证的代码9.补充:第三种…...

jQuery实现3D轮播图

通过CSS3的3D变换和jQuery Transit插件实现了一个3D旋转的图片轮播效果 HTML部分&#xff1a; div id“banner”&#xff1a;定义了一个id为"banner"的div标签&#xff0c;作为图片轮播的容器。 ul: 在"banner"中定义了一个无序列表&#xff0c;每个列表项…...

Java面试题(每天10题)-------连载(43)

目录 Spring篇 1、请举例说明Qualifier注解 2、构造方法注入和设值注入有什么区别&#xff1f; 3、Spring框架中有哪些不同类型的事件&#xff1f; 4、FileSystemResource和ClassPathResource有什么区别&#xff1f; 5、Spring框架中都用到了哪些设计模式&#xff1f; 6…...

Python高级数据结构——并查集(Disjoint Set)

Python中的并查集&#xff08;Disjoint Set&#xff09;&#xff1a;高级数据结构解析 并查集是一种用于处理集合的数据结构&#xff0c;它主要支持两种操作&#xff1a;合并两个集合和查找一个元素所属的集合。在本文中&#xff0c;我们将深入讲解Python中的并查集&#xff0…...

pytorch学习9-优化器学习

系列文章目录 pytorch学习1-数据加载以及Tensorboard可视化工具pytorch学习2-Transforms主要方法使用pytorch学习3-torchvisin和Dataloader的使用pytorch学习4-简易卷积实现pytorch学习5-最大池化层的使用pytorch学习6-非线性变换&#xff08;ReLU和sigmoid&#xff09;pytorc…...

MySQL之锁

MySQL之锁 锁是计算机在执行多线程或线程时用于并发访问同一共享资源时的同步机制&#xff0c;MySQL中的锁是在服务器层或者存储引擎层实现的&#xff0c;保证了数据访问的一致性与有效性 MySQL锁可以按模式分类为&#xff1a;乐观锁与悲观锁。 按粒度分可以分为全局锁、表级锁…...

今日现货黄金最新建议

近期现货黄金价格再度逼近历史高位&#xff0c;很多本来在场外观望的投资者&#xff0c;都纷纷希望进场一试身手。然而大涨大跌的行情并不是很适合新手投资者参与&#xff0c;如果大家还没做好技术上的准备&#xff0c;可以多听听正规交易平台的专业人士的意见。 在正式入市之前…...

基于混沌算法的图像加密解密系统

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义&#xff1a; 随着信息技术的迅猛发展&#xff0c;图像的传输和存储已经成为现代社会中不可或缺的一部分。然而&#xff0c;随着互联网的普及和信息的快速传播&am…...

vscode插件离线下载

离线下载插件地址&#xff1a;https://marketplace.visualstudio.com/VSCode...

第二十一章总结

一、网络通信&#xff1a; 1.网络程序设计基础&#xff1a;网络程序设计编写的是与其他计算机进行通信的程序。 1.1局域网与互联网&#xff1a;为了实现两台计算机的通信&#xff0c;必须用一个网络线路连接两台计算机 2.网络协议&#xff1a;网络协议规定了计算机之间连接的…...

查看端口占用并杀死进程

1.安装查看工具 sudo yum install net-tools 2.查看占用情况 netstat -tunlp | grep 8089 3.杀死进程 kill -9 227...

前后端数据传输格式(上)

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 作为后端&#xff0c;写…...

maven的package和install命令有什么区别以及Maven常用命令与GAV坐标与Maven依赖范围与Maven依赖传递与依赖排除与统一声明版本号

maven的package和install命令有什么区别以及Maven常用命令与GAV坐标与Maven依赖范围与Maven依赖传递与依赖排除与统一声明版本号 一: maven的package和install命令有什么区别 一般都与clean命令结合使用 mvn package 生成target目录&#xff0c;编译、测试代码&#xff0c;…...

【动手学深度学习】(六)权重衰退

文章目录 一、理论知识二、代码实现2.1从零开始实现2.2简洁实现 【相关总结】 主要解决过拟合 一、理论知识 1、使用均方范数作为硬性限制&#xff08;不常用&#xff09; 通过限制参数值的选择范围来控制模型容量 通常不限制偏移b 小的意味着更强的正则项 使用均方范数作为柔…...

动手学习深度学习-跟李沐学AI-自学笔记(3)

一、深度学习硬件-CPU和GPU 芯片&#xff1a;Intel or AMD 内存&#xff1a;DDR4 显卡&#xff1a;nVidia 芯片可以和GPU与内存通信 GPU不能和内存通信 1. CPU 能算出每一秒能运算的浮点运算数&#xff08;大概0.15左右&#xff09; 1.1 提升CPU利用率 1.1.1 提升缓存…...

3.2 Puppet 和 Chef 的比较与应用

Puppet 和 Chef 的比较与应用 文章目录 Puppet 和 Chef 的比较与应用Puppet 和 Chef 简介工作原理对比**模块化的重要性**&#xff1a; Puppet 和 Chef 简介 介绍 Puppet 和 Chef 这两个流行的配置管理工具的背景和用途。强调它们的共同目标&#xff1a;实现自动化的系统配置和…...

promise使用示例

下面是一个 Promise 使用示例&#xff0c;通过 Promise 实现异步操作的链式调用&#xff1a; const getUser (userId) > {return new Promise((resolve, reject) > {// 模拟异步请求setTimeout(() > {const users [{ id: 1, name: Alice },{ id: 2, name: Bob },{ …...

一起学docker系列之十四Dockerfile微服务实践

目录 1 前言2 创建微服务模块2.1 **创建项目模块**2.2 **编写业务代码** 3 编写 Dockerfile4 构建 Docker 镜像5 运行 Docker 容器6 测试微服务7 总结8 参考地址 1 前言 微服务架构已经成为现代软件开发中的一种重要方式。而 Docker 提供了一种轻量级、便携式的容器化解决方案…...

Qt Creator 11.0.3同时使用Qt6.5和Qt5.14.2

Qt Creator 11.0.3同时使用Qt6.5和Qt5.14.2 概要方法1.打开Qt Creator中的Kit&#xff0c;这里我直接附上几张截图&#xff0c;不同的版本打开位置可能有所不同&#xff0c;总之最终目的是要打开构建套件&#xff08;Kit&#xff09;2.可以看到构建套件里面有包含了“构建套件K…...

Python中字符串列表的相互转换详解

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python编程中&#xff0c;经常会遇到需要将字符串列表相互转换的情况。这涉及到将逗号分隔的字符串转换为列表&#xff0c;或者将列表中的元素连接成一个字符串。本文将深入讨论这些情景&#xff0c;并提供丰富…...

09、pytest多种调用方式

官方用例 # content of myivoke.py import sys import pytestclass MyPlugin:def pytest_sessionfinish(self):print("*** test run reporting finishing")if __name__ "__main__":sys.exit(pytest.main(["-qq"],plugins[MyPlugin()]))# conte…...

分布式锁常见实现方案

分布式锁常见实现方案 基于 Redis 实现分布式锁 如何基于 Redis 实现一个最简易的分布式锁&#xff1f; 不论是本地锁还是分布式锁&#xff0c;核心都在于“互斥”。 在 Redis 中&#xff0c; SETNX 命令是可以帮助我们实现互斥。SETNX 即 SET if Not eXists (对应 Java 中…...

个人互动网站/seo网站优化优化排名

G 题意&#xff1a; 就是给你n个关卡&#xff0c;每个关卡有多个前置关卡&#xff0c;只有把所有的前置关卡都打败&#xff0c;自己才能进入。然后每个关卡有一个防御值和特性值&#xff0c;如果小牛攻击力比他高&#xff0c;或者小牛有这个特性值&#xff0c;那么就可以打败他…...

七牛云加速WordPress/济南seo网站优化

前段时间遇到了这个问题 也百度了很多 不过还是用自己的方法解决了 一个超级简单的方法 简单到令人发指 由于直接写文本太丑了 所以还是截图吧 嘻嘻嘻 假如有一个这样的数组 &#xff08;这是假如 可能每个人的数据都不一样哦 但是方法都是可用的&#xff09; 因为我这个项目功…...

网站中文域名好不好/seo石家庄

1. 需求 目前我们开发的TCP服务端程序只能服务于一个客户端&#xff0c;如何开发一个多任务版的TCP服务端程序能够服务于多个客户端呢? 完成多任务&#xff0c;可以使用线程&#xff0c;比进程更加节省内存资源。 2. 具体实现步骤 编写一个TCP服务端程序&#xff0c;循环等…...

农特产品网站建设合同模板/东莞百度seo

断路器 照明&#xff1a;1P 16A&#xff0c;约承受3520W功率&#xff0c;占一位&#xff1b;插座&#xff1a;1P 16A&#xff0c;约承受3520W功率&#xff0c;占一位&#xff1b;厨房、卫生间各一路&#xff1a;25A&#xff08;带漏电保护&#xff09;&#xff0c;约承受5500W…...

如何查看用wordpress建的站点/阿拉营销网站

文章目录一、题目1、题目描述2、基础框架3、原题链接二、解题报告1、思路分析2、时间复杂度3、代码详解三、本题小知识四、加群须知一、题目 1、题目描述 设计一种算法&#xff0c;将一个新节点插入到一个完全二叉树中&#xff0c;并在插入后保持其完整。实现 CBTInserter类: …...

做网站费用怎么入账/猪八戒网接单平台

用户名&#xff1a;tianfang 电子邮件&#xff1a;quart163.com 注册码&#xff1a;2NnUqd3shO2agta0xNjcusfK1LXO 给出一个C-Free 5.0中文专业版的下载地址&#xff1a;http://www.programarts.com/cfree_ch/download.htm。...