首页 > 其他分享 >POSIX 详解

POSIX 详解

时间:2023-09-27 11:45:13浏览次数:39  
标签:函数 void 详解 线程 pthread POSIX id

目录

编写跨平台应用需要考虑的问题

  假如一个美国人、一个德国人、一个法国人在一起想开展贸易,他们互相听不懂对方的语言,但是如果不懂对方语言,就无法开展贸易,怎么办呢,于是他们坐下来决定以后在贸易市场中都使用英语来交流,这样就可以互相之间谈生意了。那么在贸易市场中都使用英语来交流这个规则就是一个大家都遵守的标准

POSIX是什么,为什么需要POSIX

  POSIX 是可移植操作系统接口 Portable Operating System Interface of UNIX
的缩写, POSIX 标准定义了操作系统应该为应用程序提供的接口标准,是在各种UNIX操作系统上运行的软件的一系列 API标准的总称 。它定义了一套标准的操作系统接口和工具,最初是基于 UNIX 制定的针对操作系统应用接口的国际标准。 POSIX 是一个涵盖范围很广的标准体系,距今已经颁布了20多个标准。

  制定POSIX标准是为了获得不同操作系统在源代码级的软件兼容性,使操作系统具有较强的可移植性。换句话说,为一个POSIX兼容的操作系统编写的程序,应该可以在任何其它的POSIX操作系统(即使是来自另一个厂商)上编译执行。任何操作系统都只有符合该标准才能运行UNIX程序。

  看到这里,应该就明白了POSIX本质上就是为了让一种UNIX系统上开发的程序能在另一种UNIX系统运行而制定的一种标准,不这样的话,不同厂商的 UNIX 系统各自开发的软件都不能在别家的 UNIX 系统上运行了。而POSIX这个标准就是为了解决这个问题而生的,各厂商在开发UNIX系统时,只要遵守这个 POSIX 标准,开发出来的系统就可以运行其他遵守 POSIX 标准的软件。

  Linux就是 是一个遵循 POSIX 标准的操作系统。也就是说,任何基于 POSIX 标准编写的应用程序,包括大多数UNIX和类UNIX系统的应用程序,都可以方便地移植到Linux系统上。

  同时 POSIX 并不局限于 UNIX,许多其它的操作系统也支持POSIX,例如Windows从WinNT开始就有兼容POSIX的考虑。这是因为当年在要求严格的领域,Unix地位比Windows高。为了把Unix用户拉到Windows阵营搞的。现在情况当然有变化,与当年大不相同了。现在最新的Win10对Linux/POSIX支持好,则是因为Linux已经统治了廉价服务器市场。为了提高Windows的竞争力搞的。windows遵循这个标准的好处是软件可以跨平台。所以windows也支持就很容易理解,很多多优秀的开源软件,支持了这个这些软件就可能有windows版本,就可以完善丰富windows下的软件。

  一般情况下,应用程序通过应用编程接口(API),而不是直接通过系统调用来编程。这点很重要,因为应用程序使用API实际上并不需要和内核提供的系统调用对应。一个API定义了一组应用程序使用的编程接口。它们可以调用一个系统调用,也可以通过调用多个系统调用来实现,而不使用任何系统调用也可以。实际上,API可以在各种不同的操作系统上实现,给应用程序提供完全相同的接口,而它们本身在这些系统上的实现却不同。

  从这种意义上来说,程序员不关心系统调用,他们只需要使用好API,而操作系统只需要处理好系统调用。不同的操作系统内核实现同样的功能的方法不同,为了实现可移植性,不同操作系统需要遵循同一套标准。

  举例来说,系统A实现fork的系统调用是A_fork,系统B实现fork的系统调用是B_fork,给系统A编写的程序如果要移植到系统B上,需要修改每一处调用A_fork的代码。如果系统A和B都遵循标准POSIX,把自己的fork封装到一个通用的POSIX_fork调用里,然后把这样遵循标准的函数都集中到unistd.h头文件里,那么应用程序只需要用这个头文件里的函数就可以实现不同系统之间的移植。

POSIX线程常用API介绍

1、POSIX线程库

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以pthread_开头的
  • 要使用线程库中的函数,要通过引入头文件<pthread.h>
  • 链接这些线程函数库时要使用编译器命令的-lpthread选项

2、创建线程pthread_create

  • 功能:创建一个新的线程

  • 函数签名:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

  • 参数说明

    • thread:是一个输出型参数,返回线程ID,常用来保存线程id(无符号长整形)
    • attr:设置线程的属性,attr为NULL表示使用默认属性,一般都是NULL
    • start_routine:是个函数地址(函数指针),线程启动后要执行的函数
    • arg:传给线程启动函数的参数
  • 返回值:成功返回0;失败返回错误码

3、pthread_self

pthread_self()函数可以返回当前线程的id,这是一个pthread线程结构体的地址

示例代码

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdlib>
#include <cstring>
using namespace std;
 
void *thread_run(void* args){
    char *s = (char*)args; // 首先要将void*的参数转成对应的类型
    while(1) {
        cout << "Get the param :" << s << endl;
        printf("new thread id: 0x%x\n", pthread_self());
        sleep(2);
    }
}

int main() {
    char *s = new char[6];
    strcpy(s,"zebra");
    pthread_t tid;
    pthread_create(&tid, NULL, thread_run, s);
    while ( true ) {
        printf("main thread id: 0x%x\n", pthread_self());
        sleep(2);
    }
}

输出
image

  可以看到,主线程的线程id和新线程的线程id是不同的,而且是地址,可以发现参数zebra成功传递到了thread_run函数中。

4、线程等待 pthread_join(主线程等待新线程)

  一般而言,线程也是需要被等待的,如果不等待,可能会导致类似于"僵尸进程"的问题 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。

  • 功能:等待线程结束,调用该函数的线程将挂起等待,直到id为thread的线程终止(也就是主线程阻塞式等待id为thread的进程终止)
  • 函数签名:int pthread_join(pthread_t thread, void **value_ptr);
  • 参数说明
    • thread:线程ID
    • value_ptr:是一个输出型参数,返回线程执行完函数后的返回值(这个函数的返回值也就是这个线程的返回值),我们创建函数的时候,传入的线程执行函数返回值是void*的,所以这里定义一个指向void*类型变量的指针。
  • 返回值:成功返回0;失败返回错误码

  列如:主线程等待其他线程执行完毕后才能继续执行(只能用这种for循环的方式,一个个等)

  注:如果在c++中,虽然可以把int转换成void*类型,但是无法将void*转回int,会报错,因为你是64位机器,void*是8字节,int是4字节,会有精度丢失,c++不允许这种精度丢失,c语言允许。

  所以如果在c++中,如果我们要使用void*类型的8个字节存储实际内容(void*类型本身就可以充当容器),我们需要使用long,将void*转成long类型,然后再转回去,不会有精度丢失,因为long在64位下是8字节

代码举例(使用void*存储参数的值)

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

const int NUM = 10;

void *run(void *param) {
    // int data = *(int*)param;  // 如果在c++中,虽然可以把int转换成void*类型,但是无法将void*转回int,会报错,因为你是64位机器,void*是8字节,int是4字节,会有精度丢失,c++不允许这种精度丢失,c语言允许
    // 所以如果在c++中,我们需要使用long,将void*转成long类型,然后再转回去,不会有精度丢失,因为long在64位下是8字节
    long data = (long)param;
    printf("current thread is %d\n",data);
    return (void*)data;
}

int main()
{
    pthread_t tid[NUM];
    for (int i = 0; i < NUM; i++) {
        pthread_create(&tid[i],NULL,run,(void*)i);
        usleep(100000);
    }
    void *status = NULL;
    int ret = 0;
    for(int i = 0; i < NUM; i++){
        ret = pthread_join(tid[i],&status);
        printf("ret: %d, status: %d\n", ret, (long)status);
    }
    return 0;
}

  输出结果

image

1、将参数i转换成void*类型,传到 run 方法内部,然后强转成 long 后,可以成功获取到参数 i 的值。

2、pthread_join函数可以等待线程,并通过一个输出型参数获取函数的返回值,也就是线程的返回值。

注意:创建线程执行完对应的函数后,有三种情况:

  • 代码跑完结果对

  • 代码跑完结果不对

  • 代码异常
    前两种pthread_join可以通过输出型参数status来判断代码结果是否正确,并作出处理 ,第三种代码异常,pthread_join不需要处理,处理异常是进程来做的。

线程终止的方案

  • 函数中return
    • main函数退出return的时候代表主线程and进程退出
    • 其他线程函数return,只代表当前线程退出
  • 新线程通过pthread_exit终止自己(exit是终止进程,不要在其他线程中调用,如果你就想终止一个线程的话! !)
  • 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

pthread_exit函数

  • 功能:终止调用该函数的线程
  • 函数签名: void pthread_exit(void *value_ptr);
  • 参数说明
    • value_ptr:value_ptr不要指向一个局部变量,一个输入型参数。这个参数会作为线程退出后的返回值
  • 返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

  注意:pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

pthread_cancel函数

  • 功能:取消一个执行中的线程,取消线程ID为传入参数的线程
  • 函数签名:int pthread_cancel(pthread_t thread);
  • 参数说明:
    • thread:线程ID(可以在创建的时候用变量把线程id保存下来,在这里就可以拿来用)
  • 返回值:成功返回0;失败返回错误码

  注意事项: 取消线程以后,线程退出的返回值是-1(对应的是PTHREAD_CANCELED宏)

线程分离pthread_detach函数

  默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。

  如果不关心线程的返回值,join是一种负担,这个时候可以告诉系统,当线程退出时,自动释放线程资源线程分离,分离之后的线程不需要被join(不能被join)运行完毕之后,会自动释放Z状态的pcb

函数签名:int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:pthread_detach(pthread_self());

pthread中的线程id与Linux内核中的轻量级线程id的区别

  我们看到的线程id是pthread库的线程id,不是Linux内核中的LWP(light weight process),pthread库的线程id是一个内存地址,而Linux内核中的LWP是一个数值。

  1、我们使用多线程需要用到pthread库,这是一个采用动态链接的共享库,加载到内存最后都放在栈和堆之间的共享区(共享内存,动态库都是放在这里的),内存中只有一份。(通过线程自己的页表映射到同一个物理空间)

  每个线程都要有运行时的临时数据,每个线程都要有自己的私有栈结构,描述线程的用户级控制块。

  2、每一个新线程创建的时候,都会在pthread库里创建一个pthread结构体(每个线程都使用虚拟地址空间-mm_struct,注意task_struct包括mm_struct,每个线程有一个task_struct也就是PCB,其内部的mm_struct里面的共享区里面存放有所有线程的pthread结构体),用来保存线程的相关信息(线程的用户级数据,线程的私有栈),有100个线程,那在共享区内部的pthread库里面就会有100个用来保存pthread信息的结构体。

  在这个pthread库中,如何快速找到对应的pthread结构体呢,只要拿到线程id就行,线程id就是pthread结构体的地址,只要拿到这个地址,我们就可以很轻松地找到pthread,获取线程运行时的用户级数据。

  3、用户层调用pthread_create创建一个线程的时候,会在共享区内部创建一个pthread结构体,保存线程的栈,用户级数据(临时数据)等信息;返回给用户的线程id就是这个pthread结构体的地址。同时,在Linux内核中,也要为线程创建对应的pcb(CPU调度由pcb说了算)。其中,也要创建一个与当前线程id对应的lwp(在pthread结构体里面保存lwp,内核的pcb里面保存线程id,也就是pthread结构体的地址)。

  用户级线程1:1式的和Linux内核中的轻量级线程对应,这就是Linux实现线程的方式。

参考

标签:函数,void,详解,线程,pthread,POSIX,id
From: https://www.cnblogs.com/hhddd-1024/p/17732334.html

相关文章

  • 测试驱动技术(TDD)系列之3:详解Java数组
    在前面的文章中我介绍了如何通过junit4和TestNG实现参数化,这两种架构都通过二维数组来实现参数化,在这里我就给大家详细的介绍一下java数组。Junit4定义参数化数据,代码如下:publicstaticCollectionprepareData(){Object[][]object={{1,2,3},{0,2,2},{0,3,3}};returnArrays.as......
  • 测试驱动技术(TDD)系列之2:详解TestNG参数化
    上一篇文章介绍了测试驱动的相关概念,并以junit4为例,带大家了解如何在测试框架中实现测试驱动。详情:测试驱动技术(TDD)系列之1:一文带你上手测试数据驱动大家会发现Junit4在同一个测试类中实现多组数据的数据驱动,不是很方便,需要我们自己解决!我也说过在TestNG中这个问题很容易搞定!给自己......
  • Java面向对象概念详解
    对象对象有两个层次的概念,现实生活中对象指的是可观世界的实体;而程序中对象就是一组变量和相关方法的集合,其中变量表明对象的状态,方法表明对象所具有的行为。可以将现实生活中的对象经过抽象,映射为程序中的对象,对象是对现实的高度抽象。类对象在程序中是通过一种抽象数据类型来描述......
  • 详解如何使用VS code搭建JavaScript环境(适合小白)
    对于从事自动化测试的同学来说,有很多自动化测试项目是需要使用JavaScript脚本语言进行coding的,包括selenium、playwright、Puppeteer,那么选择哪种IDE合适呢?在这里我推荐visualstudiocode,即vscode!注意:本文介绍的是JavaScript在后端运行和调试的方法,并未涉及前端(浏览器)相关开发......
  • 详解Windows 安装Docker Desktop(百分百成功)
    相信IT职场同学目前对docker这个概念已经不在陌生,docker在运维同学和开发同学中应用比较广泛,测试同学想要进行实操则在很大层度上需要运维同学的配合(在指定的服务器上进行),这就需要看别人脸色行事,或多或少会有少许不爽;加之现在各种工具也都提供了docker镜像,我们在做工具调研时可......
  • 详解分布式系统核心概念——CAP、CP和AP
    最近研究Sykwalking,当调研oap如何进行集群部署时发现:skywalkingoap之间本身不能搭建集群,需要一个集群管理器来组建集群,它支持nacos、zookeeper、Kubernetes、Consul、Etcd五种集群管理器。我重点比较了nacos和zookeeper,发现二者最大的区别是Zookeeper采用了CP架构,nacos既支持CP......
  • 铷原子钟(铷钟)时间频率标准技术参数详解
    铷原子钟(铷钟)时间频率标准技术参数详解铷原子钟(铷钟)时间频率标准技术参数详解京准电子科技官微——ahjzsz铷原子频率标准主要有高精度授时型GPS北斗接收机、高性能铷原子钟、低相噪锁相电路、控制模块和高精度滤波模块组成。通过数字锁相技术综合了GPS北斗的长期稳定性、铷原子......
  • 详解git pull命令和使用过程中遇到的常见问题:fatal: ‘origin‘ does not appear to b
    使用gitpull同步远程代码使用git管理测试相关代码时,因为测试代码本身量级不大,所以很少使用分支,默认都使用master(主分支),当我们想要从远程origin(origin是远程仓remoterepository,clone到本地的默认名字)。当主机的master分支拉取代码过来和本地的当前分支进行合并时,需要使用如下命令:g......
  • 详解使用VS code搭建C语言环境遇到的那些坑(适合小白)
    Vscode搭建C语言环境为啥想起来要装c环境?是因为亲属家的大一新生小朋友问我关于c的问题!好吧,学c语言那是20年前的事儿了,但是在小朋友面前也不能跌份,于是乎准备温习一下c!一切代码都的从选IDE开始,网上看了一下,现在比较多的就是推荐visualstudio(vs)和visualstudiocode(vscode),如果想......
  • 搞定!详解MeterSphere 配置外部Mysql5.7的全过程
     最近试用了MeterSphere做接口测试平台,感觉使用起来非常方便,最重要的是开源免费!官方文档还是非常详细的,这里我就不多介绍了,感兴趣的同学可以参考:https://metersphere.io/docs/v2.x/经过讨论,决定在测试团队推广。由于公司数据库管理策略,数据库必须通过dba统一管理,所以需要MeterSph......