Linux下的系统编程——守护进程、线程(十二)

简介: Linux下的系统编程——守护进程、线程(十二)

一、进程组和会话

       进程组,也称之为作业。BSD 于 1980 年前后向 Unix 中增加的一个新特性。代表一个或多个进程的集合。每个 进程都属于一个进程组。在 waitpid 函数和 kill 函数的参数中都曾使用到。操作系统设计的进程组的概念,是为了简 化对多个进程的管理。

       当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组 ID==第一个进程 ID(组长进程)。 所以,组长进程标识:其进程组 ID==其进程 ID

可以使用 kill -SIGKILL -进程组 ID(负的)来将整个进程组内的进程全部杀死。                                

       组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。

二、守护进程:

1.守护进程:

       daemon(精灵)进程。通常运行与操作系统后台,脱离控制终端。一般不与用户直接交互。周期性的等待某个事件发生或周期性执行某一动作

       不受用户登录注销影响。通常采用以d结尾的命名方式

2.守护进程创建步骤:

   1. fork子进程,让父进程终止

   2. 子进程调用 setsid() 创建新会话

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

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

       022 -- 755    0345 --- 432   r---wx-w-   422

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

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

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
void sys_err(const char *str){
  perror(str);
  exit(1);
}
 
int main(int argc,char *argv[])
{
  pid_t pid;
  int ret,fd;
 
  pid = fork();    //创建进程
  if(pid > 0)      //父进程终止
    exit(0);
 
  pid = setsid();    //创建新会话
  if(pid == -1)
    sys_err("setsid error");
    
  ret = chdir("/home/book/Desktop");    //改变工作目录位置
  if(ret == -1)
    sys_err("chdir error");
 
  umask(0022);    //改变文件访问权限掩码
 
  close(STDIN_FILENO);    //关闭文件描述符 0
 
  fd = open("/dev/null",O_RDWR);  //fd --> 0
  if(fd == -1)
    sys_err("open error");
 
  dup2(fd,STDOUT_FILENO);    //重定向stdout
  dup2(fd,STDERR_FILENO);    //重定向stderr
  
  while(1);    //模拟守护进程业务
 
  return 0;
}

用户的登录注销不影响进程

三、线程

1.三级映射:

三级映射:

       进程 PCB --> 页目录(可看成数组,首地址位于 PCB 中) --> 页表 --> 物理页面 --> 内存单元

2.线程概念:

   进程:有独立的进程地址空间。有独立的pcb。        分配资源的最小单位。

   线程:有独立的pcb。没有独立的进程地址空间。   最小单位的执行。

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

最小执行单位:

3.线程共享:

      独享栈空间内核栈、用户栈

       共享 ./text./data ./rodataa ./bsss heap  ---> 共享【全局变量】(errno)

(1)线程共享资源

               1).文件描述符表

               2).每种信号的处理方式

               3).当前工作目录

               4).用户 ID 和组 ID

               5).内存地址空间 (.text/.data/.bss/heap/共享库)

(2) 线程非共享资源

               1).线程 id

               2).处理器现场和栈指针(内核栈)

               3).独立的栈空间(用户空间栈)

               4).errno 变量

               5).信号屏蔽字

               6).调度优先级

4.线程优、缺点

       优点: 1. 提高程序并发性   2. 开销小   3. 数据通信、共享数据方便

       缺点: 1. 库函数,不稳定   2. 调试、编写困难、gdb 不支持  3. 对信号支持不好

       优点相对突出,缺点均不是硬伤。Linux 下由于实现方法导致进程、线程差别不是很大。

5.线程控制原语:

1)获取线程id

线程id是在进程地址空间内部,用来标识线程身份的id号。

pthread_t pthread_self(void);            

       返回值:本线程id

2) 创建子线程。

int pthread_create(pthread_t tid, const pthread_attr_t attr, void (start_rountn)(void ), void arg);    

       参1:传出参数,表新创建的子线程 id

       参2:线程属性。传NULL表使用默认属性

       参3:子线程回调函数。创建成功,pthread_create函数返回时,该函数会被自动调用。

       

       参4:参3的参数。没有的话,传NULL

       返回值:成功:0

           失败:errno

获取线程id、创建子线程:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
//子线程
void *tfn(void *arg)    //子进程回调函数
{
    printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());//获取进程id
 
    return NULL;
}
 
//主线程
int main(int argc, char *argv[])
{
    pthread_t tid;
 
    int ret = pthread_create(&tid, NULL, tfn, NULL);    //创建子进程
    if (ret != 0) {
        perror("pthread_create error");
    }
 
    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());//获取进程id
    //sleep(1);
 
  //return 0;
    pthread_exit((void *)0);
}

3)循环创建N个子线程:

      for (i = 0; i < 5; i++)

          pthread_create(&tid, NULL, tfn, (void )i);   // 将 int 类型 i, 强转成 void , 传参。    

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
void *tfn(void *arg)
{
    int i = (int)arg;           //强转.受参数类型限制
    sleep(i);
    printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
 
    return NULL;
}
 
int main(int argc, char *argv[])
{
    int i;
    int ret;
    pthread_t tid;
 
    for (i = 0; i < 5; i++) {
        ret = pthread_create(&tid, NULL, tfn, (void *)i); // i 传参采用 值传递. 借助强转.
        if (ret != 0) {
            sys_err("pthread_create error");
        }
    }
 
    sleep(i);
    printf("main: I'm Main, pid = %d, tid= %lu\n", getpid(), pthread_self());
 
  return 0;
}

4)线程中全局变量共享

线程默认共享数据段、代码段等地址空间,常用的是全局变量。而进程不共享全局变量,只能借助 mmap

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
 
int var = 100;
 
void *tfn(void *arg)
{
  var = 200;
  printf("thread, var = %d\n", var);
 
  return NULL;
}
 
int main(void)
{
  printf("At first var = %d\n", var);
 
  pthread_t tid;
  pthread_create(&tid, NULL, tfn, NULL);
  sleep(1);
 
  printf("after pthread_create, var = %d\n", var);
 
  return 0;
}

如果遇到以下编译问题,不要慌张

问题的原因pthread不是linux下的默认的库,也就是在链接的时候,无法找到phread库中哥函数的入口地址,于是链接会失败。

解决:在gcc编译的时候,附加要加 -lpthread参数即可解决。

试用如下命令即可编译通过

gcc z.c -o z -lpthread

5)   退出当前线程

void pthread_exit(void retval);  

       retval:退出值。 无退出值时,NULL

       exit();    退出当前进程。

       return: 返回到调用者那里去。

       pthread_exit(): 退出当前线程。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
void *tfn(void *arg)
{
    int i = (int)arg;           //强转.受参数类型限制
    sleep(i);
    if(i == 2){
 
    }
    printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
 
    return NULL;
}
 
int main(int argc, char *argv[])
{
    int i;
    int ret;
    pthread_t tid;
 
    for (i = 0; i < 5; i++) {
        ret = pthread_create(&tid, NULL, tfn, (void *)i); // i 传参采用 值传递. 借助强转.
        if (ret != 0) {
            sys_err("pthread_create error");
        }
    }
 
    sleep(i);
    printf("main: I'm Main, pid = %d, tid= %lu\n", getpid(), pthread_self());
 
  return 0;
}

exit(0)   退出线程

/********使用exit(0)**************/
 
void *tfn(void *arg)
{
    int i = (int)arg;        
    sleep(i);
    if(i == 2){
        exit(0);    //表示退出线程
    }
    printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
 
    return NULL;
}

return NULL 返回到调用者那里

/**********使用return NULL**************/
 
void *tfn(void *arg)
{
    int i = (int)arg;          
    sleep(i);
    if(i == 2){
       return NULL;    //返回到调用者那里
    }
    printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
 
    return NULL;
}

pthread_exit(NULL)  当前线程退出

/*****使用pthread_exit(NULL)  当前线程退出********/
 
void func(void)
{
    pthread_exit(NULL);
 
    return;
}
 
void *tfn(void *arg)
{
    int i = (int)arg;      //强转.受参数类型限制
    sleep(i);
    if(i == 2){
      func();    //当前线程退出
    }
    printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
 
    return NULL;
}
 
 
 
 
/*********或者*************/
 
 
void *tfn(void *arg)
{
    int i = (int)arg;           //强转.受参数类型限制
    sleep(i);
    if(i == 2){
      pthread_exit(NULL);    //当前线程退出
    }
    printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
 
    return NULL;
}

6)   阻塞 回收线程

int pthread_join(pthread_t thread, void retval);    

       thread: 待回收的线程id

       retval:传出参数。 回收的那个线程的退出值。

           线程异常借助,值为 -1。

       返回值:成功:0

           失败:errno

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
struct thrd{
    int var;
    char str[256];
};
 
void sys_err(const char *str)
{
    perror(str);
    exit(1);
}
 
void *tfn(void *arg)
{
    struct thrd *tval;
 
    tval = malloc(sizeof(tval));
 
    tval->var = 100;
    strcpy(tval->str,"hello thread");
    
    return (void *)tval;
}
 
int main(int argc,char *argv[])
{
    pthread_t tid;
    struct thrd *retval;  
 
    int ret = pthread_create(&tid,NULL,tfn,NULL);
    if(ret != 0)
        sys_err("pthread_create error");
 
    ret = pthread_join(tid,(void **)&retval);
    if(ret != 0)
        sys_err("pthread_join error");
    
    printf("child thread exit with var = %d,str = %s\n",retval->var,retval->str);
 
    pthread_exit(NULL);
 
    return 0;
}

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
void sys_err(const char *str)
{
    perror(str);
    exit(1);
}
 
void *tfn(void *arg)
{
    
    return (void *)74;
}
 
int main(int argc,char *argv[])
{
    pthread_t tid;
 
    int *retval;
    int ret = pthread_create(&tid,NULL,tfn,NULL);
    if(ret != 0)
        sys_err("pthread_create error");
 
    ret = pthread_join(tid,(void **)&retval);
    if(ret != 0)
        sys_err("pthread_join error");
 
    printf("child thread exit with %d\n",(void *)retval);
 
    pthread_exit(NULL);
 
    return 0;
}

注意:

局部变量地址,不可做返回值

void *tfn(void *arg)
{
    struct thrd tval;    //局部变量地址,不可做返回值
 
    tval = malloc(sizeof(tval));
 
    tval->var = 100;
    strcpy(tval->str,"hello thread");
    
    return (void *)tval;
}

  7)杀死一个线程。

需要到达取消点(保存点)

int pthread_cancel(pthread_t thread);      

       thread: 待杀死的线程id

       

       返回值:成功:0

           失败:errno

       如果,子线程没有到达取消点, 那么 pthread_cancel 无效

       我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();

       成功被 pthread_cancel() 杀死的线程,返回 -1.使用pthead_join 回收

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
 
void tfn()
{
    while(1){
        printf("thread: pid = %d,tid = %lu\n",getpid(),pthread_self());
        sleep(1);
    }
    return NULL;
}
int main(int argc,char *argv[])
{
 
    pthread_t tid;
 
    int ret = pthread_create(&tid,NULL,tfn,NULL);
    if(ret != 0){
        fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
        exit(1);
    }
 
    printf("main: pid =%d,tid = %lu\n",getpid,pthread_self());
 
    sleep(5);
    
    ret = pthread_cancel(tid);
 
    if(ret != 0){
        fprintf(stderr,"pthread_cancel error:%s\n",strerror(ret));
        exit(1);
    }
    
    while(1);
    
    return 0;
}

8)设置线程分离

int pthread_detach(pthread_t thread);      

       thread: 待分离的线程id

       

           返回值:成功:0

           失败:errno    

注意:检查出错返回:  线程中:

       fprintf(stderr, "xxx error: %s\n", strerror(ret));

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
 
void *tfn(void *arg)
{
    printf("thread: pid = %d, tid= %lu\n",getpid(), pthread_self());
 
    return NULL;
}
 
int main(int argc, char *argv[])
{
   
    int ret;
    pthread_t tid;
 
  
    ret = pthread_create(&tid, NULL, tfn, NULL); // i 传参采用 值传递. 借助强转.
    if (ret != 0) {
        fprintf(stderr,"pthread_create error :%s\n",strerror(ret));
        exit(1);
    }
 
    ret = pthread_detach(tid);    //设置线程分离,线程终止,会自动清理pcb,无需回收
    if (ret != 0) {
        fprintf(stderr,"pthread_detach error :%s\n",strerror(ret));
        exit(1);
    }
 
    sleep(2);
    
    ret = pthread_join(tid,NULL);    //tid为无效值
    if (ret != 0) {
        fprintf(stderr,"pthread_join error :%s\n",strerror(ret));
        exit(1);
    }
 
    printf("main: I'm Main, pid = %d, tid= %lu\n", getpid(), pthread_self());
  
    pthread_exit((void *)0);
}
 

 

9)   线程与进程原语比较

   线程控制原语                    进程控制原语

  pthread_create()                fork();

   pthread_self()                    getpid();

   pthread_exit()                    exit(); / return

   pthread_join()                    wait()/waitpid()

   pthread_cancel()                kill()

   pthread_detach()

6.线程属性:

   设置分离属性

  pthread_attr_t attr     创建一个线程属性结构体变量

  pthread_attr_init(&attr);   初始化线程属性

   pthread_attr_setdetachstate(&attr,  PTHREAD_CREATE_DETACHED);

   设置线程属性为分离态

  pthread_create(&tid, &attr, tfn, NULL); 借助修改后的 设置线程属性 创建为分离态的新线程

  pthread_attr_destroy(&attr);   销毁线程属性

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
 
void *tfn(void *arg)
{
    printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());
 
    return NULL;
}
 
int main(int argc, char *argv[])
{
    pthread_t tid;
 
    pthread_attr_t attr;
 
    int ret = pthread_attr_init(&attr);
    if (ret != 0) {
        fprintf(stderr, "attr_init error:%s\n", strerror(ret));
        exit(1);
    }
 
    ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);      // 设置线程属性为 分离属性
    if (ret != 0) {
        fprintf(stderr, "attr_setdetachstate error:%s\n", strerror(ret));
        exit(1);
    }
 
    ret = pthread_create(&tid, &attr, tfn, NULL);
    if (ret != 0) {
        perror("pthread_create error");
    }
 
    ret = pthread_attr_destroy(&attr);
    if (ret != 0) {
        fprintf(stderr, "attr_destroy error:%s\n", strerror(ret));
        exit(1);
    }
 
    //sleep(1)    //等待子线程死亡
 
    //因为join是可以阻塞的,所以不sleep也可以
 
    ret = pthread_join(tid, NULL);    //回收子线程,回收失败线程分离
    if (ret != 0) {
        fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
        exit(1);
    }
 
    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());
 
    pthread_exit((void *)0);
}

7.线程使用注意事项

       1). 主线程退出其他线程不退出,主线程应调用 pthread_exit

       2). 避免僵尸线程

                pthread_join

                pthread_detach

               pthread_create 指定分离属性

               被 join 线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;

       3). malloc 和 mmap 申请的内存可以被其他线程释放

       4). 应避免在多线程模型中调用 fork 除非,马上 exec,子进程中只有调用 fork 的线程存在,其他线程在子进程 中均 pthread_exit

        5) . 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制


目录
相关文章
|
3天前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
50 34
|
6天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
38 17
|
15天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
50 26
|
7天前
|
消息中间件 Linux C++
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
39 16
|
1月前
|
监控 搜索推荐 开发工具
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
128 2
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
|
1月前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
117 20
|
1月前
|
消息中间件 调度
如何区分进程、线程和协程?看这篇就够了!
本课程主要探讨操作系统中的进程、线程和协程的区别。进程是资源分配的基本单位,具有独立性和隔离性;线程是CPU调度的基本单位,轻量且共享资源,适合并发执行;协程更轻量,由程序自身调度,适合I/O密集型任务。通过学习这些概念,可以更好地理解和应用它们,以实现最优的性能和资源利用。
61 11
|
1月前
|
Java Linux 调度
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
38 6
|
2月前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
121 13
|
2月前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具