Linux系统编程 day9 SIGCHLD and 线程

SIGCHLD信号

只要子进程信号发生改变,就会产生SIGCHLD信号。

借助SIGCHLD信号回收子进程

回收子进程只跟父进程有关。如果不使用循环回收多个子进程,会产生多个僵尸进程,原因是因为这个信号不会循环等待。

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/wait.h>
#include<signal.h>

void catch_child(int signo) //有子进程终止,发送sigchld信号时,该函数会被内核回调
{
    pid_t wpid;
    int status;
    //while((wpid = wait(NULL))!= -1);
    while((wpid = waitpid(-1 , &status , 0))!= 0 ){
        if(WISEXITED(status))
            printf("catch child:%d , status: %d\n" ,wpid , WEXITSTATUS(status));}
}

int main(int argc , char *argv[])
{
    pid_t pid;
    int i ;
    //阻塞
    sigset_t set ;  // 防止在父进程创建sa_mask之前子进程先死亡。
    sigemptyset(&set);
    sigaddset(&set , SIGCHLD);

    sigprocmask(SIG_BLOCK , &set , NULL);
    for(i = 0 ; i < 5 ; i++){
        if((pid = fork()) == 0)
                break;
    }

    if(i == 5){
        struct sigaction act;

        act.sa_handler = catch_child;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGCHLD , &act , NULL);

        //解除阻塞
        sigprocmask(SIG_UNBLOCK , &set , NULL);

        printf("I am parent , %d\n" , getpid());
    }else{
        printf("I am child , %d\n" , getpid());
    }

    return 0 ;
}

中断系统调用(非重点)

系统调用分为两类:慢速系统调用和其他系统调用。

慢速系统调用:可能会使进程永远阻塞的一类。eg. read write wait and so on .

其他系统调用:getpid()....

sa_flags 用来设置被信号中断后系统调用是否重启。

进程组和会话

进程组也称之为作业。

创建会话

会话是多个进程组的集合。

注意事项

创建会话不能是进程组组长,该进程变成新会话首进程。

创建新会话丢弃原有的控制终端,该会话没有控制终端。

建立新会话时,先调用fork,父进程终止,子进程调用setsid()

getsid函数

获取进程所属的会话id

cpp 复制代码
pid_t getsid(pid_t pid);
成功 返回会话id
失败 -1
ps ajx命令查看系统中的进程

组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。

setsid函数

创建会话,以自己的ID设置进程组ID,同时也是新会话的ID

cpp 复制代码
pid_t setsid(void)
成功:返回调用进程的会话id;
失败:-1

调用setsid函数的进程,既是新的会长,也是新的组长。

守护进程(daemon精灵,进程)

守护进程时Linux中的后台服务进程,通常运行与操作系统后台,脱离控制终端,一般不与用户直接交互 , 周期性的等待某个事件发生或周期性执行某一动作,不受用户登入注销影响。一般使用d结尾的名字。

守护进程创建步骤:

1、fork子进程,让父进程终止。

2、子进程调用setsid创建新会话。

3、通常根据需要,改变工作目录位置,chdir() , 防止目录被卸载

4、通常根据需要,重设置umask文件权限掩码,影响新文件的创建权限

5、通常根据需要,关闭/重定向 文件描述符

6、守护进程 业务逻辑 while()

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc , char *argv[])
{
    pid_t pid ;
    int fd ;
    pid = fork();
    if(pid > 0){
        exit(0);   //正常终止父进程
    }else{
        pid = setsid();    //创建新会话
        if(pid == -1){
            perror("set error");
            exit(1);
        }
        int ret = chdir("/home/qqq");   //改变工作目录位置
        if(ret == -1){
            perror("chdir error");
            exit(1);
        }
        umask(0022);   // 设置掩码权限

        close(STDIN_FILENO);   // 关闭文件描述符

        fd = open ("/dev/null" , O_RDWR);
        if(fd == -1){
            perror("open error");
            exit(1);
        }
        dup2(fd , STDOUT_FILENO);   //重定向
        dup2(fd , STDERR_FILENO);

        while(1);  // 模拟守护进程做的事情

    }
    return 0 ;
}

这里由于我不明白为什么需要将标准输入关闭,并且将标准输出和标准错误重定向,我上网查了一些资料:

1、为什么要关闭STDIN_FILENO?
守护进程是在后台运行的,不应该与终端交互,因此不需要标准输入,为防止错误,直接关闭。

2、为什么要将STDOUT and STDERR --->fd 0 ("/dev/null")

"/dev/null"是一个特殊设备,写的什么东西进去都会被吃掉,读取的时候什么也看不到,守护进程不应该输出信息到终端,因此重定向给 fd(此时是 /dev/null)本质上就是为了干净、不占资源、避免潜在错误。

线程(不要将线程和信号混合用)

线程 : LWP,light weight process 轻量级的进程 , 本质仍是进程(Linux环境下)

进程: 独立的进程地址空间 , 有PCB

线程:有独立的PCB,但没有独立的地址空间(共享)

Linux下 线程是最小的执行单位 , 进程是最小分配资源单位,可以看成只有一个线程的进程。

ps -Lf 进程id --->线程号 LWP --->CPU执行的最小单位。

线程可以看作寄存器和栈的集合。

线程共享资源

文件描述符表 、 每种信号的处理方式 、 当前工作目录 、 用户ID和组ID 、 内存地址空间、全局变量

线程非共享资源

线程id 、 处理器现场和栈指针(内核栈) 、独立的栈空间(用户栈) 、 error变量 、 信号屏蔽字

线程的优缺点

优点:提高程序的并发性 , 开销小 , 数据通信、共享数据方便

缺点:库函数不稳定 , 调试、编写困难、GDB不支持 , 对信号支持不友好

优点突出,缺点不明显,能使用线程使用线程

线程控制原语

pthread_self函数

获取线程ID ,注意这里的线程ID和线程号LWP不一样。

线程id是在进程地址空间内部,用来表示线程身份id号

cpp 复制代码
pthread_t pthread_self(void);
成功返回 线程id

pthread_create函数

cpp 复制代码
int pthread_create(pthread_t * tid ,const pthread_attr_t *attr , void *(*start_rountn)(void*) , void *arg);

pthread_t * tid:传出参数,传出创建子线程的id。
const pthread_attr_t *attr:线程属性 , 传NULL表示使用默认属性。
void *(*start_rountn)(void*):子线程回调函数,创建成功,pthread_create()函数返回时,该回调函数                            
                             会被自动调用。
void *arg:回调函数的参数,没有的话使用NULL

返回值 成功 0
失败errno

循环创建5个子线程。

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

void* func(void * arg)
{
    int i = (int) arg;
    printf("I am %dth child , pid:%d , tid:%lu\n" , i+1 , getpid() , pthread_self());
    return NULL;    
}


int main(int argc , char* argv[])
{
    pthread_t tid;
    int i ,ret;
    for(i = 0 ; i < 5 ; i++){
        ret = pthread_create(&tid , NULL , func , (void*)i);
        if(ret == -1){
            perror("pthread_create error");
            exit(1);
        }    
    }
    
    sleep(i);
    printf("I am main , pid:%d , tid:%lu\n" , getpid() , pthread_self());
    

    return 0 ;    
}

pthread_exit函数

将当前线程退出。

cpp 复制代码
void pthread_exit(void *retval);
retval:退出值。无退出值时,NULL

exit() ; 退出当前进程
return ; 返回到调用者那里去
pthread_exit() ; 将调用该函数的线程退出

pthread_join函数

阻塞等待线程退出,获取线程退出状态。回收子线程。

cpp 复制代码
int pthread_join(pthread_t thread, void **retval);
返回值 成功 0 
失败 -1

pthread_cancel函数

杀死(取消)线程。相当于kill函数。但是需要取消点(保存点) 。如果子线程没有到达取消点,那么该函数无效,我们可以在程序中手动添加一个取消点,使用pthread_testcancel()。成功被cancel函数杀死的进程,可以使用pthread_join回收。

cpp 复制代码
int pthread_cancel(pthread_t thread);
返回值: 成功 0 
失败 errno
相关推荐
FoldWinCard5 小时前
D5 Linux 网络及端口命令
linux·运维·服务器
零涂毕业设计6 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
tedcloud1236 小时前
OmniRoute怎么部署?开源AI模型路由平台Linux部署教程
linux·服务器·人工智能·开源·音视频
霸道流氓气质6 小时前
KMS 密钥管理服务(Key Management Service)原理与实践
linux·服务器·数据库
SakitamaX7 小时前
LVS(Linux Virtual Server)概念详解
linux·运维·lvs
小张同学a.7 小时前
Linux系统管理
linux·运维
阿龙的工作记录7 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
代码村新手8 小时前
Linux的基本指令
linux·运维·服务器
hkj88088 小时前
Ubuntu 切换java版本
java·linux·ubuntu
撩得Android一次心动10 小时前
Linux编程笔记3【个人用】
linux·笔记