Day03 linux高级系统编程--进程

概念

进程与程序的区别

进程:一个正在运行的代码就叫做进程,是动态的,会占用内存

程序:一段封装好的待运行的代码或可执行文件,是静态的,会占用磁盘空间

单道与多道程序

单道:程序一个一个排好队,一个一个执行,若代码A阻塞,则代码B不能立即执行,需等待代码A执行结束代码B才会执行

多道:程序之间相互独立,各个程序之间同时执行,各执行各的,互不影响,它们在系统管理程序控制下,相互穿插执行(并行,并发)

并行与并发的区别

并行:有多个核,一个程序对应一个核,同时执行程序

并发:有一个核,多个程序对应一个核,程序之间按顺序相互交替执行,在宏观上也是并行。

进程控制块(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。进程号是唯一的,但是进程号是可以重复使用的(前提是当前程序1抢得cpu处理权后,执行1的代码,当1代码执行结束后,会释放该进程号,这时其他进程就可以使用该进程号)。

PID 进程号

PPID 父进程号

PGID 进程组号

获取进程id

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

pid_t getpid(void);

功能:获取当前进程号

参数:无

返回值:本进程号 (失败-1)

获取进程的父进程id

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

pid_t getppid(void);

功能:获取调用此函数进程的父进程id(ppid)

参数:无

返回值:调用此函数进程的父进程id(ppid)

获取组进程号

#include <unistd.h>

#include <sys/types.h>

pid_t getpgid(pid_t pid);

功能:获取当前组进程号,也就是同组进程中第一个进程的进程号

参数:pid 进程号

返回值:当前组进程号,也就是同组进程中第一个进程的进程号

创建进程fork

概念:

父子进程

系统允许一个进程创建新进程。此新进程就是子进程,这就是创建的父子进程。

int x = fork();

函数:

#include <unistd.h>

#include <sys/types.h>

pid_t fork(void);

功能:用于进程创建一个新进程,该新进程就是该进程的子进程,原进程被称为父进程。

参数:无
返回值:

        成功:子进程返回0,父进程返回子进程id。getpid pid_t为整型。

        失败:返回-1

注意:

fork创建失败的两个主要原因:

        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");
    int id = fork();
    if(id < 0)
    {
        printf("子进程创建失败id:%d\n",id);
    }
    else if(id > 0)
    {
        printf("父进程的进程号是:%d\n,子进程的进程号是:%d\n",getpid(),id);
    }
    else if(id == 0)
    {
        printf("父进程的进程号是:%d\n,创建的子进程的进程号是:%d\n,创建的id:%d\n",getppid(),getpid(),id);
    }
    printf("德玛西亚\n");
    while(1);
    return 0;
}

父子进程关系

使用 fork 函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的
地址空间。
地址空间 : 包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先
级、进程组号等。
子进程所独有的只有它的进程号,计时器等。
因此,使用 fork 函数的代价是很大的。
示例2:
#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;
}

示例2:

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

啦啦啦在缓冲区,所以父进程打印不出来“啦啦啦”,当子进程执行时\n才会将父进程中的“啦啦啦”冲刷出来,和“德玛西亚“拼接起来一起打印,打印两遍。

分析:

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;
}

进程状态

分类:

可以分为三大状态与五大状态
三大状态
运行态,就绪态,阻塞态
五大状态
新建态、终止态,运行态,就绪态,阻塞态
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, 因为回收原则 , 谁创建谁回收 , 所以该函数在父进程中调用
示例 1
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
    int x = fork();
    if(x < 0)
    {
        printf("创建失败\n");
    }
    else if(x == 0)
    {
        for(int i = 0;i < 10; i++)
        {
            printf("子进程正在进行第%d次执行",getpid(),i);
            sleep(1);
        }
    }
    else if(x > 0)
    {

        printf("父进程正在等待子进程结束\n");
        wait(NULL);
        printf("父进程已回收子进程%u\n",x);
    }
    return 0;
}

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); // 不关心状态 直接实参为NULL
        if (WIFEXITED(status))               // 子进程正常退出
        {
            // 取出状态值
            printf("子进程%u已经结束 状态值为:%d\n", ret,
                   WEXITSTATUS(status));
        }
    }
    return 0;
}

atexit 函数
概述:
作用 : 进程在退出前可以用 atexit 函数注册退出处理函数
注意 :
1, 一个进程可以登记多至 32 个函数,这些函数将由 exit 自动调用。我们称这些函
数为终止处理程序 , atexit 函数来登记这些函数。
2, 以登记这些函数的相反顺序调用它们。同一函数如若登记多次 , 则也被调用多次
示例 1
#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;
}

示例2

#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 是有数量限制。
示例
#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("子进程");
_exit(-1);
}
else if(pid > 0)
{
while(1);
}
return 0;
}

孤儿进程

概念
父进程先结束,子进程为孤儿进程 .( 无害的 )
孤儿进程被 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 2
close(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
示例
printf ( " 所属终端名称 :%s\n" , ttyname ( 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[])
{
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 会使用当前进程执行开始的程序 , 当前进程将会被开启的程序替换

总结

程序与进程
        程序: 本质上一个可执行文件 , 静态的
        进程: 一个正在进程的程序 , 动态的 , 占据运行内存
单道程序与多道程序
        单道程序: 同时只能做一件事 ,A 阻塞 ,B 等待
        多道程序: 同时可以做多件事 ,A 阻塞 ,B 正常执行
并行与并发
        并行: 有多核 , 每个进程一个核 , 同时执行
        并发: 一个核 , 多个进程交替执行 , 在宏观上来看是并行 , 在微观上来看是交替执行
进程号 , 父进程号 , 进程组号 , 会话号 , 终端
        每个进程系统都会为其分配一个编号, 该编号就是进程号
        进程号取值范围:0~32767
        0号进程是系统的调度进程
        1号进程是直接或间接的创建了其他进程 , 所有 1 号进程其他进程直接或间接父进程 , 0号进程外, 其他进程都是 1 号进程的直接或间接子进程
一个进程创建多个进程 , 那么多个进程与该进程在同一个组中 , 这种组就是进程组
进程组号就是进程组中第一个进程的进程号
会话中包含多个进程组
终端 : 可输入输出的设备
getpid
getppid
getpgid
getpgrp
setpgid
getsid
setsid
ttyname
进程的创建
        fork
        vfork
进程的回收 子进程调用
        exit
        _exit
父进程调用
        wait
        waitpid
进程的挂起
        sleep
进程注册结束调用函数
        atexit
多进程的创建与回收
int i = 0;
for(i = 0; i < 要创建的子进程数 ; i++)
{
        int pid = fork();
        if(pid == 0)
        {
                break;
        }
}
if(i == 0)
{
}
else if(i == 1)
{
}
...
else if(i == 要创建的子进程数 )
{
        while(1)
        {
                int pid = waitpid(-1,NULL,WNOHANG);
                if(pid < 0)
                {
                        break;
                }
        }
}
特殊进程
1, 僵尸进程 : 子进程结束 , 父进程未回收 , 有危害
2, 孤儿进程 : 父进程先结束 , 剩下的子进程就是孤儿进程 , 将被其父进程的父进程接收 ,
危害
3, 守护进程 :
又名 : 精灵进程 , 不死进程
特点 : 不能被关闭
步骤 :
1, 忽略挂断信号
2, 创建子进程 , 关闭父进程
3, 将创建的子进程单独成为会话
4, 更改地址
5, 修改权限
6, 关闭所有已开启的文件描述符
7, 循环处理核心代码
在当前进程中启动其他进程
exec 函数族
system

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[NAND Flash 2.1] NAND Flash 闪存改变了现代生活

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《深入理解NAND Flash》 <<<< 返回总目录 <<<< ​ 1989年NAND闪存面世了&#xff0c;它曾经且正在改变了我们的日常生活。 NAND 闪存发明之所以伟大&#xff0c;是因为&#xff0c…

Hello World

世界上最著名的程序 from fastapi import FastAPIapp FastAPI()app.get("/") async def root():return {"message": "Hello World"}app.get("/hello/{name}") async def say_hello(name: str):return {"message": f"…

Vue2与Vue3的语法对比

Vue2与Vue3的语法对比 Vue.js是一款流行的JavaScript框架&#xff0c;通过它可以更加轻松地构建Web用户界面。随着Vue.js的不断发展&#xff0c;Vue2的语法已经在很多应用中得到了广泛应用。而Vue3于2020年正式发布&#xff0c;带来了许多新的特性和改进&#xff0c;同时也带来…

D. In Love

贪心&#xff0c;维护最靠左的右端点以及最靠右的左端点 // Problem: D. In Love // Contest: Codeforces - Codeforces Round 905 (Div. 3) // URL: https://codeforces.com/contest/1883/problem/D // Memory Limit: 256 MB // Time Limit: 2000 ms // // Powered by CP Edi…

一:C语言常见概念

一&#xff1a;C语言常见概念 1.认识C语言&#xff1a; ​ C语言是人和计算机交流的语言 ​ C语言是一门面向过程的语言&#xff0c;而C&#xff0c;Java&#xff0c;Python等是一门面向对象的语言 ​ 软件开发&#xff08;项目&#xff09;&#xff1a;面向过程面向对象 …

芯片半导体科普

我们在日常工作和生活中&#xff0c;经常会使用到各种各样的电子或电器产品&#xff0c;例如电脑、手机、电视、冰箱、洗衣机等。 这些产品&#xff0c;如果我们把它拆开&#xff0c;都会看到类似下面这样的一块绿色板子。 有时候是蓝色或黑色的 大家都知道&#xff0c;这个绿…

Android开发之横屏模式布局

创建一个同名的layout文件 Android Studio会创建res/layout-land目录&#xff0c;并放入一个名为activity_main.xml的新布局文件中。要查看新建文件和文件夹&#xff0c;可把项目工具窗口切换至Project视角模式&#xff1b;要查看文件汇总&#xff0c;请切回Android视角模式。…

基于jsp+servlet+mybatis的简易在线选课系统

目录 一.数据库 1.数据库和表的创建 2.数据插入 二.代码实现 1.pojo类 &#xff08;1&#xff09;Course &#xff08;2&#xff09;User &#xff08;3&#xff09;Elective 2.mapper接口 &#xff08;1&#xff09;UserMapper &#xff08;2&#xff09;ElectiveMap…

Park Unpark

文章目录 当先调用park时&#xff1a;如果_counter0&#xff0c;这时候该线程阻塞&#xff0c;进入_cond阻塞&#xff0c;之后Unpark设置_counter为1后停止阻塞 当先调用Unpark时&#xff1a;此时先将_counter设置为1&#xff0c;当后面出现park时一判断_counter为1&#xff0c…

IntelliJ IDEA 之初体验

文章目录 第一步&#xff1a;下载与安装 IntelliJ IDEA1&#xff09;官网下载2&#xff09;选择那种安装包3&#xff09;开始下载4&#xff09;解压 第二步&#xff1a;启动 IntelliJ IDEA第三步&#xff1a;创建第一个 Java 项目第四步&#xff1a;运行第一个 Java 程序1&…

Java注册并监听全局快捷键

背景 之前在博客中分享了SWT托盘功能, 随之带来一个问题, 当程序最小化后无法快速唤醒, 按照平时使用软件的思路, 自然想到了注册全局快捷键, 本文介绍使用java方式实现全局快捷键的注册. 方案 通过google,搜到一个现成的库: jintellitype, 使用maven可以直接引用, 非常方便…

使用TouchSocket适配一个c++的自定义协议

这里写目录标题 说明一、新建项目二、创建适配器三、创建服务器和客户端3.1 服务器3.2 客户端3.3 客户端发送3.4 客户端接收3.5 服务器接收与发送 四、关于同步Send 说明 今天有小伙伴咨询我&#xff0c;他和同事&#xff08;c端&#xff09;协商了一个协议&#xff0c;如果使…

http和https的区别有哪些

目录 HTTP&#xff08;HyperText Transfer Protocol&#xff09; HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09; 区别与优势 应用场景 未来趋势 当我们浏览互联网时&#xff0c;我们经常听到两个常用的协议&#xff1a;HTTP&#xff08;HyperText Tra…

【LeetCode刷题-链表】--92.反转链表II

92.反转链表II /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; }* }*/ cla…

项目中使用之Maven BOM

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 工具教程 ✨特色专栏&#xff1a; MyS…

MYSQL全语法速查(含示例)

文章目录 1.从简单的查询开始查找所有记录(SELECT *)查找记录中的所有登录名(SELECT)查找登录名为admin的密码(WHERE)查找电话号码非空的记录(IS NOT NULL)查找所在城市为北京或者用户名字是李四的记录(OR)查找所在城市为北京并且用户名字是张三的记录(AND)查找用户名字是李四或…

【节日专栏】Python海龟绘制圣诞树代码

圣诞节就要到了&#xff0c;有没有要送给朋友礼物的程序员呢&#xff1f; import turtledef tree(d, s):if d < 0:returnturtle.forward(s)tree(d - 1, s * 0.8)turtle.right(120)tree(d - 3, s * 0.5)turtle.right(120)tree(d - 3, s * 0.5)turtle.right(120)turtle.backw…

4.10 构建onnx结构模型-Softmax

前言 构建onnx方式通常有两种&#xff1a; 1、通过代码转换成onnx结构&#xff0c;比如pytorch —> onnx 2、通过onnx 自定义结点&#xff0c;图&#xff0c;生成onnx结构 本文主要是简单学习和使用两种不同onnx结构&#xff0c; 下面以 Softmax 结点进行分析 方式 方法一…

神通数据库备份

备份策略&#xff1a; 每月1、15日00:00全量物理备份 每周一凌晨02:00差异物理备份 其余每天凌晨03:00增量物理备份 使用的工具&#xff1a; 数据库维护工具 环境&#xff1a;windows11 step1&#xff1a;创建全量/差异/增量物理备份调度任务 其中&#xff1a; 备份模式&…

C语言之多维数组

所谓多维数组就是以多个数组为单位组成的数组&#xff0c;即元素本身是数组的数组。下面我们来学习多维数组的基本知识&#xff1a; 多维数组 上一节学习的数组都是int型或double型等单一类型&#xff0c;实际上数组本身也可以作为组成数组的元素。 以数组作为元素的数组时二…