Linux 学习记录25(进程线程篇)

本文详细介绍了Linux中的守护进程创建过程,包括创建孤儿进程、设置SID、修改目录、文件描述符管理和重定向输出等步骤。接着,文章讨论了多线程的概念,展示了如何创建和管理线程,以及涉及的内存问题、执行顺序、线程ID获取、退出、资源回收和信号处理。此外,还提到了线程互斥锁的使用,以防止竞态条件,并提供了多线程拷贝文件的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux 学习记录25(进程线程篇)

在这里插入图片描述

一、守护进程(daemon)

所谓守护进程,其实就是一个服务,随着系统的启动而启动,随着系统的结束而结束,不依赖于终端而存在因为在终端上运行的进程,随着终端被关闭,进程也随之关闭而且一般不会被打断,需要将其执行放在根目录下

1. 守护进程创建流程

1. 创建一个孤儿进程
2. 重新设置孤儿进程的SID(会话id)和组id (让进程独立出来)
3. 修改守护进程的目录为根目录,当然,也可以不修改,但是一旦所在的目标被删除,守护进程也被删除了
4. 修改创建文件的掩码给最大权限 (umask(0))
5. 将标准输入,标准输出、标准出错重定向到某个文件 (dup2)
6. 对应的文件就是日志文件

2. 创建守护进程所需的api

(1. 创建会话

函数原型:pid t setsid(void);
功能:创建一个会话,将当前的进程的id设置为pgid,和sid
参数1:无
返回值:成功返回会话的id失败返回-1并置位错误码

(2. 切换目录

函数原型:int chdir(const char *path);
功能:切换目录
参数1:要切换的目录,是一个字符串
返回值:成功返回失败返回-1并置位错误码

(3. 更改掩码

函数原型:mode t umask(mode t mask);
功能:更改掩码
参数1:要修改的值
返回值:永远成功,不会失败

(4. 获取程序中最大文件描述符

函数原型:int getdtablesize(void);
功能:回去程序中能打开的最大文件描述符的值(1024)
参数1:无
返回值:成功返回当前进程文件描述符的最大个数,不会失败

3. 创建守护进程

(1. 创建一个孤儿进程

int main(int argc, char const *argv[])
{
    pid_t pid = fork();

    if(pid < 0)
    {//如果创建失败
        printf("Error:");
        return -1;
    }else if(pid == 0)
    {//子进程
        while (1);//让子进程一直执行
    }else
    {//关闭父进程,让子进程成为孤儿进程
        exit(EXIT_SUCCESS);
    }

    return 0;
}

(2. 设置孤儿进程的SID(会话ID)和组ID,使进程独立出来

int main(int argc, char const *argv[])
{
    pid_t pid = fork();

    if(pid < 0)
    {//如果创建失败
        printf("Error:");
        return -1;
    }else if(pid == 0)
    {//子进程
        /*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
        setsid();//设置SID(会话ID)和组ID
        while (1);//让子进程一直执行
    }else
    {//关闭父进程,让子进程成为孤儿进程
        exit(EXIT_SUCCESS);
    }

    return 0;
}

(3. 更改进程的目录到根目录

int main(int argc, char const *argv[])
{
    pid_t pid = fork();

    if(pid < 0)
    {//如果创建失败
        printf("Error:");
        return -1;
    }else if(pid == 0)
    {//子进程
        /*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
        setsid();//设置SID(会话ID)和组ID
        /*更改进程的目录到根目录*/
        chdir("/");
        while (1);//让子进程一直执行
    }else
    {//关闭父进程,让子进程成为孤儿进程
        exit(EXIT_SUCCESS);
    }

    return 0;
}

(4. 修改创建文件的掩码给最大权限

int main(int argc, char const *argv[])
{
    pid_t pid = fork();

    if(pid < 0)
    {//如果创建失败
        printf("Error:");
        return -1;
    }else if(pid == 0)
    {//子进程
        /*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
        setsid();//设置SID(会话ID)和组ID
        /*更改进程的目录到根目录*/
        chdir("/");
        /*修改创建文件的掩码给最大权限*/
        umask(0);
        while (1);//让子进程一直执行
    }else
    {//关闭父进程,让子进程成为孤儿进程
        exit(EXIT_SUCCESS);
    }

    return 0;
}

(5. 将文件打开的所有文件描述符关闭

int main(int argc, char const *argv[])
{
    pid_t pid = fork();

    if(pid < 0)
    {//如果创建失败
        printf("Error:");
        return -1;
    }else if(pid == 0)
    {//子进程
        /*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
        setsid();//设置SID(会话ID)和组ID
        /*更改进程的目录到根目录*/
        chdir("/");
        /*修改创建文件的掩码给最大权限*/
        umask(0);
        /*将文件打开的所有文件描述符关闭*/
        for (int i = 0; i < getdtablesize(); i++)
        {
            close(i);
        }
        
        while (1);//让子进程一直执行
    }else
    {//关闭父进程,让子进程成为孤儿进程
        exit(EXIT_SUCCESS);
    }

    return 0;
}

(6. 将标准输入,输出,出错重定向到日志文件

int main(int argc, char const *argv[])
{
    pid_t pid = fork();

    if(pid < 0)
    {//如果创建失败
        printf("Error:");
        return -1;
    }else if(pid == 0)
    {//子进程
        /*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
        setsid();//设置SID(会话ID)和组ID
        /*更改进程的目录到根目录*/
        chdir("/");
        /*修改创建文件的掩码给最大权限*/
        umask(0);
        /*将文件打开的所有文件描述符关闭*/
        for (int i = 0; i < getdtablesize(); i++)
        {
            close(i);
        }
        /*将标准输入,输出,出错重定向到日志文件*/
        int fd;
        if((fd = open("log.txt",Ap))
        {
            perror("open: ");
        }
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        
        while (1);//让子进程一直执行
    }else
    {//关闭父进程,让子进程成为孤儿进程
        exit(EXIT_SUCCESS);
    }

    return 0;
}

(7. 执行守护进程

int main(int argc, char const *argv[])
{
    pid_t pid = fork();

    if(pid < 0)
    {//如果创建失败
        printf("Error:");
        return -1;
    }else if(pid == 0)
    {//子进程
        /*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
        setsid();//设置SID(会话ID)和组ID
        /*更改进程的目录到根目录*/
        chdir("/");
        /*修改创建文件的掩码给最大权限*/
        umask(0);
        // /*将文件打开的所有文件描述符关闭*/
        // for (int i = 0; i < getdtablesize(); i++)
        // {
        //     close(i);
        // }
        /*将标准输入,输出,出错重定向到日志文件*/
        int fd;
        if((fd = open("log.txt",Ap,0664)) == -1) 
        {
            perror("open");
            return -1;
        }
        // dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        // dup2(fd, STDERR_FILENO);
        
        /*对应文件就是日志文件,执行服务功能*/
        int line = 1;
        while(1)
        {//执行守护进程
            printf("%d : 这是守护进程\r\n",line++); 
            if(line==10) break;
        }
        while(1);
    }else
    {//关闭父进程,让子进程成为孤儿进程
        exit(EXIT_SUCCESS);
    }

    return 0;
}

运行需要管理员权限
在这里插入图片描述
在目录下查看日志文件
在这里插入图片描述
查看守护进程
在这里插入图片描述
需要管理员权限关闭守护进程
在这里插入图片描述

二、多线程

1. 多线程概念

头文件: #include <pthread.h>

  1. 多线程(LWP轻量版的进程) :线程是粒度更小的处理单元
  2. 进程是资源分配的最小单位,线程是调度器进行调度的最小单位
  3. 线程共享进程的资源,多进程拥有自己独立的资源
  4. 线程几乎不占用资源,只占用的很小的有关执行状态的资源,大概在(8K)左右
  5. 线程由于共用进程的资源,所以多线程没有多进程安全,使用多线程是因为开销较小
  6. 在一个进程内,至少有一个线程(主线程)
  7. 因为线程操作函数需要依赖于第三方库,所以,想使用线程处理函数,需要安装对应的库
指令:sudo apt-get install manpages-posix-dev

在这里插入图片描述

2. 多线程创建(pthread_create)

由于线程处理函数是第三方库提供,所以,编译时需要引入相关库,需要加上 -pthread

函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
功能:创建线程
参数1:要创建的线程的线程号(以参数的形式返回)
参数2:线程的属性,一般填NULL即可
参数3:线程处理函数,是一个参数为void*类型,返回值为void"类型的函数指针
	需要传递进来的是线程处理函数的函数名
参数4:向线程处理函数中传递的参数
返回值:成功返回0,失败返回错误码
====================================================================
/*定义线程处理函数*/
void *task(void *arg)
{
    printf("子线程\r\n");
    while(1);
}

int main(int argc, char const *argv[])
{
    pthread_t thid;//线程号
    if(pthread_create(&thid, NULL,task,NULL) != 0)
    {//如果创建失败
        perror("pthread");
        return -1;
    }

    printf("主线程\r\n");
    while(1);//防止进程结束
    return 0;
}

运行:
在这里插入图片描述

3. 多线程内存问题

同一个进程内的所有线程共享进程内的所有资源

/*定义一个全局变量*/
int num = 1000;

/*定义线程处理函数*/
void *task1(void *arg)
{
    num += 100;
    printf("子线程1 num: %d\r\n",num);
    while(1);
}
/*定义线程处理函数*/
void *task2(void *arg)
{
    num -= 200;
    printf("子线程2 num: %d\r\n",num);
    while(1);
}
int main(int argc, char const *argv[])
{
    pthread_t tid1;//线程号
    pthread_t tid2;//线程号
    if(pthread_create(&tid1, NULL,task1,NULL) != 0)
    {//如果创建失败
        perror("pthread task1");
        return -1;
    }
    if(pthread_create(&tid2, NULL,task2,NULL) != 0)
    {//如果创建失败
        perror("pthread task2");
        return -1;
    }
    num -= 300;
    printf("主线程 num: %d\r\n",num);
    while(1);//防止进程结束
    return 0;
}
输出>>
子线程1 num: 1100
子线程2 num: 700
主线程 num: 900

4. 多线程执行顺序

多线程之间执行顺序没有要求,遵时间片轮询,上下文切换原则

5. 多线程获取线程ID(pthread_self)

函数原型:
功能:获取当前线程的1线程号
参数1:无
返回值:成功返回当前线程的线程号,不会失败
====================================
/*定义线程处理函数*/
void *task(void *arg)
{
    printf("子线程\r\n");
    printf("子程号:%#lx\r\n",pthread_self());
    while(1);
}

int main(int argc, char const *argv[])
{
    pthread_t thid;//线程号
    if(pthread_create(&thid, NULL,task,NULL) != 0)
    {//如果创建失败
        perror("pthread");
        return -1;
    }

    printf("主线程\r\n");
    printf("主线程的子线程号:%#lx\r\n",thid);
    printf("主线程的线程号:%#lx\r\n",pthread_self());
    while(1);//防止进程结束
    return 0;
}
输出>>
主线程
主线程的子线程号:0x7ff1da18e700
主线程的线程号:0x7ff1da9ae740
子线程
子程号:0x7ff1da18e700

6. 多线程退出(pthread_exit)

如果在子线程中使用exit(EXIT_SUCCESS),则,整个进程都会退出
所以需要使用专门退出线程的函数

函数原型:void pthread_exit(void *retval);
功能:退出所在的线程
参数1:退出线程时的状态,一般填NULL即可
返回值:无

7. 多线程资源回收(pthread_join)

函数原型:int pthread_join(pthread_t thread, void **retval);
功能:阻塞等待回收线程的资源,并为线程收尸
参数1:线程号
参数2:线程退出时的状态,一般未NULL
返回值:成功返回0,失败返回错误码。
-===================================
int main(int argc, char const *argv[])
{
    pthread_t tid1;//线程号
    pthread_t tid2;//线程号
    if(pthread_create(&tid1, NULL,task1,NULL) != 0)
    {//如果创建失败
        perror("pthread task1");
        return -1;
    }
    if(pthread_create(&tid2, NULL,task2,NULL) != 0)
    {//如果创建失败
        perror("pthread task2");
        return -1;
    }
    printf("主线程 \r\n");
    pthread_join(tid1,NULL);//阻塞等待线程回收
    pthread_join(tid2,NULL);
    pthread_exit(EXIT_SUCCESS);
    return 0;
}

8. 多线程发信号(pthread_cancel)

(1. pthread_cancel

函数原型:int pthread_cancel(pthread_t thread);
功能:给一个线程给另一个线程发送一个取消信号
参数1:线程号
返回值:成功返回0,失败返回非0的错误码

(2. pthread_setcancelstate

函数原型:int pthread_setcancelstate(int state, int *oldstate);
功能:
参数1:状态
	PTHREAD_CANCEL_ENABLE :可用状态(可被打断)
	PTHREAD_CANCEL_DISABLE :不可以状态(不可被打断)
参数2:旧的状态,一般NULL
返回值:成功返回0,失败返回错误码

9. 多线程分离态(pthread_detach)

对于线程而言,有两个状态,分别是结合态和分离态,默认是结合态,结合态的线程,结束后需要使用pthread_join回收资源
分离态的线程,不需要使用pthread_join回收资源

函数原型:int pthread_detach(pthread_t thread);
功能:将线程标记为分离态
参数1:线程号
返回值:成功返回e,失败返回错误码

三、多线程互斥

1. 多线程竞态现象

  1. 临界资源:在多线程中,多个线程能够共享的资源
  2. 临界区:访问临界资源的代码就叫临界区
  3. 互斥:同一时间内只允许一个线程拥有临界资源
  4. 同步:要求安顺序执行,安顺序访问临界资源
    当一个临界资源被多个线程同时访问时,这种现象叫做竞态
    要解决这个问题就需要使用线程的同步互斥原则

2. 多线程互斥

与斥是多个线程共同强占临界资源的过程中,只有抢占成功,就可以操作临界资源,如果没有抢占成功,就不能操作临界资源,这里没有体现按顺序进行使用临界资源问题,可能在某个线程执行多次后,才抢占到临界资源,也有可能永远枪不到,还有可能是多个线程同时抢到,为了解决这个问题,我们引入互斥机制,互斥机制解决多个线程同时抢到临界资源问题但是不解决是否抢占成功问题

3. 线程互斥锁API

#include <pthread.h>
       //1、定义一个互斥锁
        pthread_mutex_t mutex;      
        //2、c初始化互斥锁
        //静态初始化

       pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;

       pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

       pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;      
       //动态初始化一个互斥锁         

       int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
       功能:初始化一个互斥锁
       参数1:互斥锁指针
       参数2:互斥锁属性一般为:NULL
       返回值:总是返回0
       
        //3、加锁

       int pthread_mutex_lock(pthread_mutex_t *mutex);
       功能:获取锁
       参数:互斥锁
       返回值:成功返回0,失败返回非0的错误码       

       int pthread_mutex_trylock(pthread_mutex_t *mutex);
       //跟lock功能一致
       
        //4、释放锁

       int pthread_mutex_unlock(pthread_mutex_t *mutex);
       功能:释放锁
       参数:互斥锁
       返回值:成功返回0,失败返回非0的错误码 
       
        //5、销毁锁

       int pthread_mutex_destroy(pthread_mutex_t *mutex);
       功能:销毁锁
       参数:互斥锁
       返回值:成功返回0,失败返回非0的错误码 

使用

pthread_mutex_t mutex;//定义互斥锁

/*定义线程处理函数*/
void *task1(void *arg)
{
    pthread_mutex_lock(&mutex);//给临界区上锁
    printf("子进程任务结束 关闭进程\r\n");
    pthread_mutex_unlock(&mutex);//给临界区解锁锁
    pthread_exit(NULL);//退出线程
}
int main(int argc, char const *argv[])
{
    pthread_t tid1;//线程号

    pthread_mutex_init(&mutex,NULL);//初始化互斥锁

    if(pthread_create(&tid1, NULL,task1,NULL) != 0)
    {//如果创建失败
        perror("pthread task1");
        return -1;
    }
    
    printf("主线程 \r\n");
    pthread_join(tid1,NULL);//阻塞等待线程回收
    printf("线程1回收完成\r\n");
    //6、销毁锁
    pthread_mutex_destroy(&mutex);
    pthread_exit(EXIT_SUCCESS);
    return 0;
}

四、

思维导图

在这里插入图片描述

练习

1. 使用多线程拷贝图片(文件)

(1. 主函数

#include "public.h"

int main(int argc, char const *argv[])
{
    /*获取文件信息*/
    file.filename_cp[0]='.';
    file.filename_cp[1]='/';
    file.filename[0]='.';
    file.filename[1]='/';
    strcpy(file.filename_cp+2,argv[1]);
    strcpy(file.filename+2,argv[2]);
    struct stat sb;
    /*获取文件属性*/
    stat(file.filename_cp, &sb);//打开文件
    file.size = sb.st_size;

    pthread_t tid1;//线程号
    pthread_t tid2;//线程号
    if(pthread_create(&tid1, NULL,task1,&file) != 0)
    {//如果创建失败
        perror("pthread task1");
        return -1;
    }
    if(pthread_create(&tid2, NULL,task2,&file) != 0)
    {//如果创建失败
        perror("pthread task2");
        return -1;
    }
    printf("主线程 \r\n");
    pthread_join(tid1,NULL);//阻塞等待线程回收
    printf("线程1回收完成\r\n");
    pthread_join(tid2,NULL);
    printf("线程2回收完成\r\n");
    pthread_exit(EXIT_SUCCESS);
    return 0;
}

(2. 子线程1

/*定义线程处理函数*/
void *task1(void *arg)
{
    file_type* file = (file_type*)arg;
    int fp1;
    int fp2;
    int i = 0;
    char ch = 0;

    if((fp1 = open(file->filename_cp,R)) == -1)
    {//文件打开失败
        perror("open file");
        pthread_exit(NULL);//退出线程
    }

    if((fp2 = open(file->filename,Wp,0664)) == -1)
    {//文件打开失败
        perror("open file");
        pthread_exit(NULL);//退出线程
    }

    for(i = 0;i<(file->size/2);i++)
    {
        read(fp1,&ch,1);//读文件
        write(fp2,&ch,1);
    }
	close(fp1);
    close(fp2);
    printf("子进程任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}

(3. 子线程2

/*定义线程处理函数*/
void *task2(void *arg)
{
    file_type* file = (file_type*)arg;
    int fp1;
    int fp2;
    int i = 0;
    char ch = 0;

    if((fp1 = open(file->filename_cp,R)) == -1)
    {//文件打开失败
        perror("open file");
        
        pthread_exit(NULL);//退出线程
    }

    if((fp2 = open(file->filename,Wp,0664)) == -1)
    {//文件打开失败
        perror("open file");
        pthread_exit(NULL);//退出线程
    }
    /*两个光标向后偏移*/
    lseek(fp1,file->size-(file->size/2),SEEK_SET);
    lseek(fp2,file->size-(file->size/2),SEEK_SET);
    for(i = 0;i<(file->size-(file->size/2));i++)
    {
        read(fp1,&ch,1);//读文件
        write(fp2,&ch,1);
    }
	close(fp1);
    close(fp2);
    printf("子进程任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}

(4. 头文件

#ifndef __PUBLIC_H_
#define __PUBLIC_H_

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/wait.h>
#include <pthread.h>

#define R O_RDONLY
#define W O_WRONLY|O_CREAT|O_TRUNC
#define A O_WRONLY|O_CREAT|O_APPEND
#define Rp O_RDWR
#define Wp O_RDWR|O_CREAT|O_TRUNC
#define Ap O_RDWR|O_CREAT|O_APPEND

typedef struct
{
    int size;//文件大小
    char filename_cp[50];//被拷贝的文件名
    char filename[50];//文件2
}file_type;

extern file_type file;

/*定义线程处理函数*/
void *task1(void *file);
/*定义线程处理函数*/
void *task2(void *file);

#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值