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);//不关心状态 直接实参为NULL
        if(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 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
#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 会使用当前进程执行开始的程序 , 当前进程将会被开启的程序替换
  • 25
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值