祝各位道友念头通达
GitHub Gitee 语雀 打赏

编程常识

虚拟化技术 和 多个进程之间 的关系

相似之处:
隔离性: 虚拟化技术和 Linux 操作系统都提供了隔离不同应用程序或工作负载的能力。在虚拟化中,这是通过虚拟机(VM)或容器实现的,而在 Linux 中,这是通过进程隔离和权限管理实现的。

资源管理: 虚拟化技术和 Linux 操作系统都涉及对系统资源(如 CPU、内存、存储)的管理。它们都允许多个应用程序或工作负载共享同一台物理计算机,同时保持彼此之间的隔离。

关键区别:
虚拟化的层次: 虚拟化技术通常在操作系统之上运行,创建一个或多个虚拟环境,每个环境都可以运行独立的操作系统。例如,使用 KVM、VirtualBox 或 Docker 等虚拟化技术可以在一台物理计算机上运行多个虚拟机或容器,每个都可以拥有自己的操作系统。

Linux 进程: Linux 操作系统自身是一个多用户、多任务的操作系统,它本身就具有进程隔离和资源管理的能力。在 Linux 中,多个进程可以并发运行,彼此之间是相互隔离的。这种隔离是通过操作系统的调度器和权限管理来实现的。

性能开销: 虚拟化通常会引入一些额外的性能开销,因为虚拟机或容器需要在虚拟化层和物理硬件之间进行交互。相比之下,在 Linux 中运行多个进程通常会更加轻量,因为它们共享同一个操作系统内核。

适用场景: 虚拟化通常用于创建独立的虚拟环境,每个环境可以运行不同的操作系统和应用程序。这在服务器虚拟化和云计算中很常见。Linux 进程则更适用于在同一台机器上运行多个相关但相对独立的应用程序。

动态装载, 动态链接, 静态链接

动态装载和链接(Dynamic Loading and Linking)是一种在程序运行时加载和链接代码的技术。它允许程序在运行时根据需要加载和卸载代码,从而实现动态扩展和灵活性。

动态装载和链接通常与共享库(Shared Library)一起使用。共享库是一组可重用的代码和数据,可以被多个程序共享和调用。通过动态装载和链接,程序可以在运行时加载共享库,并使用其中的函数和变量。

动态装载和链接的主要优势包括:

节省内存:共享库可以被多个程序共享,避免了重复加载和占用内存的问题。

灵活性:程序可以根据需要动态加载和卸载共享库,从而实现动态扩展和灵活性。

更新和维护:通过动态装载和链接,可以轻松地更新和维护共享库,而无需重新编译和重新链接整个程序。

在动态装载和链接的过程中,通常涉及以下几个步骤:

动态加载:程序使用动态加载器(Dynamic Loader)在运行时加载共享库。动态加载器负责解析共享库的依赖关系,并将共享库加载到程序的地址空间中。

符号解析:在加载共享库时,动态加载器会解析共享库中的符号(函数和变量),并将其与程序中的符号进行链接。这样,程序就可以调用共享库中的函数和使用其中的变量。

地址重定位:在链接过程中,动态加载器会对共享库中的地址进行重定位,以适应程序的地址空间。这样,共享库中的代码和数据就可以正确地访问和使用。

需要注意的是,动态装载和链接是一项复杂的技术,涉及到库的版本兼容性、符号解析和链接等问题。在使用动态装载和链接时,需要仔细处理错误和异常情况,并确保正确地加载和使用共享库。

动态装载

动态装载(Dynamic Loading)是一种在程序运行时加载和链接代码的技术。它允许程序在运行时根据需要加载和卸载代码,从而实现动态扩展和灵活性。

在动态装载中,代码通常以共享库(Shared Library)的形式存在。共享库是一组可重用的代码和数据,可以被多个程序共享和调用。通过动态装载,程序可以在运行时加载共享库,并使用其中的函数和变量。

动态装载的主要优势包括:

节省内存:共享库可以被多个程序共享,避免了重复加载和占用内存的问题。

灵活性:程序可以根据需要动态加载和卸载共享库,从而实现动态扩展和灵活性。

更新和维护:通过动态装载,可以轻松地更新和维护共享库,而无需重新编译和重新链接整个程序。

在 Linux 和 FreeBSD 等类 Unix 系统中,动态装载通常使用动态链接器(Dynamic Linker)来实现。动态链接器负责在程序运行时解析和加载共享库,并将其链接到程序的地址空间中。

在 C/C++ 程序中,可以使用 dlopen() 函数来动态加载共享库,使用 dlsym() 函数来获取共享库中的函数和变量的地址,使用 dlclose() 函数来卸载共享库。

以下是一个简单的示例,演示如何使用动态装载加载共享库并调用其中的函数:

#include <stdio.h>
#include <dlfcn.h>

int main() {
    void* handle;
    int (*add)(int, int);

    // 动态加载共享库
    handle = dlopen("libexample.so", RTLD_LAZY);
    if (handle == NULL) {
        fprintf(stderr, "Failed to load shared library: %s\n", dlerror());
        return 1;
    }

    // 获取共享库中的函数地址
    add = dlsym(handle, "add");
    if (add == NULL) {
        fprintf(stderr, "Failed to get function address: %s\n", dlerror());
        dlclose(handle);
        return 1;
    }

    // 调用共享库中的函数
    int result = add(2, 3);
    printf("Result: %d\n", result);

    // 卸载共享库
    dlclose(handle);

    return 0;
}

在这个示例中,我们使用 dlopen() 函数加载名为 "libexample.so" 的共享库,使用 dlsym() 函数获取共享库中的函数地址,然后调用该函数。最后,我们使用 dlclose() 函数卸载共享库。

需要注意的是,动态装载是一项复杂的技术,涉及到库的版本兼容性、符号解析和链接等问题。在使用动态装载时,需要仔细处理错误和异常情况,并确保正确地加载和使用共享库。

动态链接

动态链接是一种在程序运行时将代码和库文件进行连接的技术。它允许程序在运行时动态加载和链接库文件,以便在程序中使用库中的函数和变量。

下面是动态链接的一般步骤:

  • 编写源代码:首先,你需要编写你的程序源代码。这些源代码可以包含你自己的代码以及你想要使用的库的函数和变量。

  • 编译源代码:使用编译器将源代码编译成目标文件。目标文件是机器代码的一种形式,它包含了你的程序的二进制表示。

  • 链接目标文件:使用链接器将目标文件与库文件进行链接。链接器会解析你的程序中对库函数和变量的引用,并将它们与库文件中的实际函数和变量进行关联。

  • 生成可执行文件:链接器将目标文件和库文件合并成一个可执行文件。这个可执行文件包含了你的程序的完整功能。

  • 运行程序:最后,你可以运行生成的可执行文件。在运行时,操作系统会加载可执行文件,并将其中的代码和库文件加载到内存中。程序可以在运行时动态地调用库函数和变量。

需要注意的是,动态链接可以减小可执行文件的大小,因为库文件可以在多个程序之间共享。此外,动态链接还允许你在不重新编译程序的情况下更新库文件,从而方便了程序的维护和升级。

动态装载和动态链接的区别

动态装载是指在程序运行时,根据需要将某个模块或库文件加载到内存中。这种方式可以延迟加载,只有在需要使用某个模块或库文件时才会加载到内存中,从而减少了程序的启动时间和内存占用。动态装载可以通过操作系统提供的动态链接库(Dynamic Link Library,DLL)机制来实现。

动态链接是指在程序运行时,将程序所需要的外部函数或变量的地址绑定到程序中。这种方式可以将程序的编译和链接过程分离,使得程序可以在运行时动态地加载和链接外部函数或变量。动态链接可以通过操作系统提供的动态链接器(Dynamic Linker)来实现。

区别如下:

  • 动态装载是将模块或库文件加载到内存中,而动态链接是将外部函数或变量的地址绑定到程序中。
  • 动态装载可以延迟加载,只有在需要使用时才会加载到内存中,而动态链接是在程序运行时进行的。
  • 动态装载可以减少程序的启动时间和内存占用,而动态链接可以使程序在运行时动态地加载和链接外部函数或变量。
  • 动态装载是通过操作系统提供的动态链接库机制实现的,而动态链接是通过操作系统提供的动态链接器实现的。

总的来说,动态装载和动态链接都是在程序运行时进行的,但动态装载是将模块或库文件加载到内存中,而动态链接是将外部函数或变量的地址绑定到程序中。它们的主要区别在于实现方式和作用。

linux中系统调用和动态装载关系

linux执行用户态的应用程序是通过系统调用完成的

系统调用是操作系统提供给应用程序的一种接口,用于访问操作系统的功能和资源。它允许应用程序通过调用特定的函数来请求操作系统执行某些操作,例如文件操作、进程管理、网络通信等。系统调用是操作系统内核提供的一组函数,应用程序可以通过这些函数来与操作系统进行交互。

系统调用的实现方式可以有多种,其中一种常见的方式是通过动态装载来实现。动态装载是指在程序运行时,根据需要加载和链接特定的库文件,以获取所需的函数和资源。在系统调用的情况下,操作系统会提供一个动态链接库,应用程序在运行时可以通过加载该库来获取系统调用的函数。

动态装载的好处是可以在程序运行时根据需要加载所需的函数,而不是在编译时将所有函数都链接到程序中。这样可以减小程序的体积,并且可以根据不同的操作系统和硬件平台加载不同的库文件,以实现跨平台的兼容性。

总结来说,系统调用是操作系统提供给应用程序的接口,用于访问操作系统的功能和资源。动态装载是一种实现系统调用的方式,它允许应用程序在运行时根据需要加载所需的函数和资源。

linux 执行用户态程序的步骤

在Linux系统中,执行用户态应用程序的具体代码和步骤如下:

  • 应用程序的代码被编译成可执行文件,通常是二进制文件。
  • 用户通过终端或图形界面启动应用程序。
  • 操作系统内核根据用户的请求创建一个新的进程来运行应用程序。
  • 内核为新进程分配资源,包括内存空间、文件描述符等。
  • 内核将应用程序的可执行文件加载到进程的内存空间中。
  • 内核设置进程的上下文,包括栈、寄存器等。
  • 内核将控制权转移到应用程序的入口点,开始执行应用程序的代码。
  • 应用程序按照代码的逻辑执行,可以调用系统调用来访问操作系统提供的功能。
  • 当应用程序执行完毕或遇到错误时,会返回执行结果或错误码。
  • 内核回收进程的资源,并将控制权返回给用户。
    需要注意的是,具体的代码和步骤可能会因为不同的Linux发行版和应用程序而有所差异,但以上是一般情况下执行用户态应用程序的基本过程。

exec 函数族

exec

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
  • exec函数族是Linux操作系统中的一组函数,用于在当前进程中执行一个新的程序。它们可以用于替换当前进程的映像,从而运行一个不同的程序。exec函数族包括以下几个函数:

  • execve函数:这是exec函数族中最常用的函数之一。它接受三个参数,分别是要执行的程序的路径名、命令行参数数组和环境变量数组。execve函数会将当前进程的映像替换为指定程序的映像,并开始执行该程序。

  • execv函数:这个函数与execve函数类似,但它只接受两个参数,分别是要执行的程序的路径名和命令行参数数组。环境变量将继承自当前进程。

  • execvp函数:这个函数与execv函数类似,但它会在PATH环境变量指定的目录中搜索要执行的程序。也就是说,你可以只指定程序的名称,而不需要指定完整的路径名。

  • execl函数:这个函数接受可变数量的参数,用于指定要执行的程序的路径名、命令行参数以及环境变量。参数列表以NULL结尾。

  • execle函数:这个函数与execl函数类似,但它还接受一个额外的参数,用于指定环境变量数组。

  • execlp函数:这个函数与execl函数类似,但它会在PATH环境变量指定的目录中搜索要执行的程序。

这些exec函数族的共同特点是,它们在执行成功时不会返回,而是直接开始执行新的程序。如果执行失败,这些函数会返回-1,并设置errno变量来指示错误的原因。

linux 执行应用程序具体代码调用

execve() #用户态
 ->do_sys_execve() #内核态
  ->do_execve()(check if file exist and if can be runed by current user) #内核
    ->search_binary_handler() #内核
	 ->load_xxx_binary()  #调用对应文件类型加载二进制文件
  • 在用户态下调用execve(),引发系统中断后,在内核态执行的相应函数是do_sys_execve(),而do_sys_execve()会调用do_execve()函数。

  • do_execve()首先会读入可执行文件,如果可执行文件不存在,会报错。然后对可执行文件的权限进行检查。如果文件不是当前用户是可执行的,则execve()会返回-1,报permission denied的错误。否则继续读入运行可执行文件时所需的信息(见struct linux_binprm)。

  • search_binary_handler(),根据可执行文件的类型(如shell,a.out,ELF等),查找到相应的处理函数(系统为每种文件类型创建了一个struct linux_binfmt,并把其串在一个链表上,执行时遍历这个链表,找到相应类型的结构。如果要自己定义一种可执行文件格式,也需要实现这么一个handler)。然后执行相应的load_binary()函数开始加载可执行文件。

  • 加载elf类型文件的handler是 load_elf_binary(),它先读入ELF文件的头部,根据ELF文件的头部信息读入各种数据(header information)。再次扫描程序段描述表,找到类型为PT_LOAD的段,将其映射(elf_map())到内存的固定地址上。

    • 如果没有动态链接器的描述段,把返回的入口地址设置成应用程序入口。完成这个功能的是start_thread(),start_thread()并不启动一个线程,而只是用来修改了pt_regs中保存的PC等寄存器的值,使其指向加载的应用程序的入口。这样当内核操作结束,返回用户态的时候,接下来执行的就是应用程序了。
    • 如果应用程序中使用了动态链接库,就没有那么简单了,内核除了加载指定的可执行文件,还要把控制权交给动态连接器(program interpreter,ld.so in linux)以处理动态链接的程序。内核搜寻段表,找到标记为PT_INTERP的段中所对应的动态连接器的名称,并使用load_elf_interp()加载其映像,并把返回的入口地址设置成load_elf_interp()的返回值,即动态链接器入口。当execve退出的时候动态链接器接着运行。动态连接器检查应用程序对共享连接库的依赖性,并在需要时对其进行加载,对程序的外部引用进行重定位。然后动态连接器把控制权交给应用程序,从ELF文件头部中定义的程序进入点开始执行。(比如test.c中使用了userlib.so中函数foo(),在编译的时候这个信息被放进了test这个ELF文件中,相应的语句也变成了call fakefoo()。当加载test的时候,知道foo()是一个外部调用,于是求助于动态链接器,加载userlib.so,解析foo()函数地址,然后让fakefoo()重定向到foo(),这样call foo()就成功了。)

linux IPC通信时加锁(互斥访问的一种)的本质是用信号量么

是的,Linux IPC(Inter-Process Communication,进程间通信)中可以使用信号量来加锁。信号量是一种同步原语,用于协调多个进程对共享资源的访问。它可以用于实现互斥锁(mutex)或者控制资源的使用数量。

在Linux中,通过System V IPC机制或者POSIX IPC机制,可以使用信号量进行进程间通信。System V IPC提供了三种类型的信号量:二进制信号量(binary semaphore)、计数信号量(counting semaphore)和自旋锁(spinlock)。

二进制信号量(binary semaphore)是一种用于互斥访问的信号量,只能取0或1两个值,类似于互斥锁。

计数信号量(counting semaphore)也可以用于互斥访问,但它可以取多个非负整数值,用于表示资源的可用数量。

自旋锁(spinlock)是一种特殊的信号量,它在尝试获取锁时循环忙等待,不会引起进程阻塞。

通过使用这些信号量以及相应的系统调用(如semget、semop和semctl),可以实现进程间的加锁和解锁操作,确保对共享资源的互斥访问。注意,加锁机制的实现可能涉及操作系统的特定细节和限制,因此具体的使用方式可能会有所不同。

栈帧

linux当中 程序控制块中 (struct task_struct) 中 ( unsigned long stack) 属性保存了当前线程运行的起始栈地址, 而且线程的栈实在用户空间分配的, 而不是内核空间

在Linux中,栈帧是一种用于支持函数调用和返回的数据结构,它会在函数调用时被创建并存储在线程的栈空间中。每个函数调用都会创建一个新的栈帧,用于存储函数的局部变量、函数参数和其它相关的信息。

栈帧在函数调用和返回的过程中起着重要的作用,主要包括以下几点:

存储局部变量:栈帧用于存储函数中定义的局部变量。在函数被调用时,局部变量的内存空间会在栈帧中分配,函数执行过程中可以使用这些局部变量。

传递函数参数:函数的参数是通过栈帧在函数调用时传递的。调用者将参数值保存在当前栈帧中,被调用函数可以通过栈帧访问这些参数值。

保存返回地址:栈帧中包含了返回地址,即调用函数执行完毕后需要返回到的指令地址。在函数调用时,当前函数的返回地址会被保存到栈帧中,在函数返回时根据该地址进行返回。

管理函数调用顺序:由于栈帧的特性,每个函数调用都会创建一个新的栈帧,并将其放入栈结构中。这样,可以以后进先出(LIFO)的方式管理函数的调用顺序,保证函数之间正确地进行调用和返回。

栈帧是函数调用过程中的重要组成部分,它在函数调用和返回的时候起到关键的作用,保证了函数的顺利执行,并且提供了一种局部变量和参数的存储和访问机制。

栈帧和程序控制块之间的关系-linux

在Linux中,栈帧(Stack Frame)和程序控制块(Program Control Block,PCB)是两个独立的概念,用于支持和管理不同的功能和任务。

栈帧是指在函数调用过程中,为了支持函数的执行而在堆栈上分配的一段内存空间。它用于存储函数的参数、局部变量以及返回地址等信息。每当函数被调用时,一个新的栈帧就会被创建,并随着函数调用链的增长而增加。栈帧的主要目的是为了保持函数调用的上下文和局部状态,以便在函数执行完毕后能正确返回到调用者。

而程序控制块(PCB)是操作系统用于管理和维护进程或线程状态的数据结构。PCB存储了与进程或线程相关的各种信息,包括进程标识符(PID)、寄存器状态、进程状态、资源管理信息等。PCB的主要作用是跟踪和管理进程或线程的执行状态,以便操作系统进行进程调度、资源分配和管理。

栈帧和程序控制块之间的关系是,每个进程或线程都拥有一个独立的程序控制块,其中包含了进程或线程的状态和相关信息。当进程或线程执行函数调用时,每个函数调用都会产生一个新的栈帧,用于支持函数的执行。栈帧中的信息(如局部变量、函数参数等)是存储在进程或线程的堆栈空间中的,并与程序控制块中的寄存器状态和其他信息相配合,完成函数的执行和处理函数调用关系。

总结起来,栈帧是为了函数调用和执行而在堆栈上分配的内存空间,用于维护函数执行的上下文和局部状态;而程序控制块是操作系统用于管理和维护进程或线程状态的数据结构,包含了与进程或线程相关的各种信息。

linux 上下文切换时, 当前环境和状态保存在什么位置

具体是保存在: (struct task_struct) 中 (struct thread_struct thread) 属性中, 具体可查看 __switch_to 函数

在Linux中,当发生线程上下文切换时,线程的当前环境和状态会被保存在线程控制块(Thread Control Block,TCB)中。TCB是一个数据结构,用于管理和跟踪线程的执行信息。

具体而言,TCB包含了保存线程状态的各种寄存器(例如程序计数器、栈指针、通用寄存器等),以及与线程执行相关的其他信息,如线程的优先级、堆栈信息、锁的状态等。

当发生线程上下文切换时,操作系统会保存当前线程的上下文(即TCB中的内容),然后加载下一个需要执行的线程的TCB,并将其上下文恢复,使该线程能够继续执行。

TCB的实际存储位置和组织方式可能会因不同的操作系统和线程模型而有所差异,但一般情况下,TCB被操作系统维护在内核内存中。

需要注意的是,线程的上下文保存和恢复是由操作系统内核负责的,开发者无需直接操作TCB。线程库和操作系统提供相关的API和机制,使开发者能够创建、管理和控制线程的执行。

TCB 线程控制块具体是什么
在Linux内核中,对于线程控制块(Thread Control Block,TCB),通常被称为任务结构(Task Structure),Linux使用了一个名为task_struct的数据结构来表示。

task_struct是Linux内核中非常重要的数据结构之一,用于跟踪和管理进程(包括线程)的执行状态和资源。它在内核的调度、进程管理等方面发挥着关键的作用。

task_struct结构体包含了许多信息,其中主要的成员包括:

进程标识符(PID)和父进程标识符(PPID):用于唯一标识进程和与其他进程之间的关联。

进程状态(Task State):表示进程当前的状态,如运行、就绪、阻塞等。

寄存器和堆栈指针:用于保存和恢复进程的运行上下文,即进程的寄存器状态和当前的堆栈位置。

进程优先级(Priority):用于调度决策,确定进程在可运行队列中的优先级。

资源管理信息:进程所拥有的资源,如文件描述符表、地址空间、信号处理器等。

线程标识符(TID):用于唯一标识线程。

task_struct结构体在Linux内核中以一种高度优化和紧凑的方式实现,以保证性能和内存效率。由于Linux内核是开源的,task_struct的定义和实现细节可以在内核源代码中找到(通常是位于include/linux/sched.h文件中)。

需要注意的是,由于Linux内核的不断演进和发展,task_struct的实现可能会在不同的内核版本中有所差异,但其核心目标是为了在内核中管理和调度进程和线程。

linux线程切换的主要函数
在Linux内核中,上下文切换是由内核的调度器(Scheduler)完成的,涉及多个函数和子系统之间的复杂交互。以下是一些与Linux上下文切换相关的重要函数:

schedule():这是Linux内核中最重要的上下文切换函数之一。它由调度器调用,负责切换当前运行的进程或线程,将CPU资源分配给下一个要运行的进程或线程。

switch_to():这个函数用于执行实际的上下文切换操作,切换到指定进程或线程的执行环境。它负责保存当前进程或线程的上下文,并恢复下一个进程或线程的上下文。

context_switch():这个函数是在具体的体系结构中实现的,主要完成从一个任务的上下文切换到另一个任务的工作。它涉及到保存寄存器、切换堆栈和页表等操作。

fork()/clone():这些函数用于创建新的进程或线程,并为新的进程或线程分配相应的资源。在创建新进程或线程时,会涉及到上下文切换的过程。

这里列举的函数仅仅是涉及到上下文切换的一部分函数,实际上,上下文切换过程还涉及到一系列与调度和进程管理相关的函数。

需要注意的是,这些函数的具体实现可能因Linux内核版本和体系结构而有所不同。如果你想深入了解具体实现代码,可以查阅Linux内核源代码的相关部分。

__switch_to 流程介绍

__switch_to 是 Linux 内核中实现实际上下文切换的关键函数之一。它用于切换当前运行的进程或线程,并将 CPU 的控制权转移到另一个进程或线程。

以下是 __switch_to 函数的大致流程:

首先,__switch_to 函数会保存当前任务的上下文,包括将寄存器的值保存到当前任务的任务结构(task_struct)中。这样做是为了确保在切换回当前任务时能够恢复到之前的状态。

接下来,__switch_to 函数会从目标任务的任务结构中恢复目标任务的上下文,包括将寄存器的值加载到相应的寄存器中。这样做是为了将 CPU 的控制权转移到目标任务。

在切换完成后,__switch_to 函数会返回,并将控制权转到目标任务的入口点,使其开始执行。

需要注意的是,__switch_to 函数的具体实现可能因操作系统版本和体系结构的不同而有所不同。它通常是用汇编语言编写的,以便直接操作 CPU 寄存器和堆栈。不同的体系结构可能会有不同的专用指令或约定来处理上下文切换操作。

__switch_to 函数通常由调度器和其他上下文切换相关的函数调用。它是内核调度和切换进程/线程的关键组成部分,确保任务之间的切换能够顺利进行,并保留任务的状态。

BSD 函数

BSD(Berkeley Software Distribution)是一个基于Unix的操作系统,它包含了一系列的系统调用和库函数。以下是一些常见的BSD函数:

socket函数:用于创建一个套接字,用于网络通信。
bind函数:将一个套接字绑定到一个特定的IP地址和端口号。
listen函数:将一个套接字设置为监听状态,等待连接请求。
accept函数:接受一个连接请求,并创建一个新的套接字用于与客户端通信。
connect函数:与服务器建立连接。
send函数和recv函数:用于发送和接收数据。
close函数:关闭一个套接字。
select函数:用于多路复用IO操作,可以同时监视多个文件描述符的状态。
fork函数:创建一个新的进程。
exec函数:用于在当前进程中执行一个新的程序。
pipe函数:创建一个管道,用于进程间通信。
signal函数:用于设置信号处理函数。
这些函数只是BSD中的一部分,还有很多其他的函数可供使用。请注意,BSD函数在不同的操作系统中可能会有一些差异,因此在使用时请参考相关的文档和手册。

POSIX

POSIX(Portable Operating System Interface for Unix)是一组定义了操作系统接口的标准,旨在促进不同UNIX系统之间的软件可移植性。POSIX标准旨在确保软件在符合POSIX规范的操作系统上可以编译和运行,而无需进行大量的修改。

POSIX标准涵盖了许多不同的领域和功能,包括:

进程管理:创建、终止和控制进程的接口,如fork、exec等。
文件操作:打开、读取、写入、关闭文件的接口,如open、read、write等。
目录操作:管理文件系统目录的接口,如opendir、readdir等。
系统调用:访问底层操作系统功能的接口,如调度、内存管理、设备驱动等。
线程管理:创建、同步和控制线程的接口,如pthread_create、pthread_join等。
信号处理:注册、处理和发送信号的接口,如signal、kill等。
网络编程:进行网络通信的接口,如socket、bind、connect等。
POSIX标准是为了增强不同UNIX系统之间的可移植性和互操作性而设计的。通过使用符合POSIX规范的编程接口,开发人员可以编写可在不同的POSIX兼容系统上运行的软件。这使得软件开发和移植变得更加方便和简单。

请注意,POSIX标准有不同的版本,如POSIX.1、POSIX.2、POSIX.1b等。每个版本都有特定的功能和规范要求。对于特定的POSIX函数,建议参考相关的文档和手册以了解其使用方法和规范要求。

linux 下对线程的操作

如何取消线程

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* thread_func(void* arg) {
    printf("Thread is running\n");

    // 检查取消请求
    while(1) {
        pthread_testcancel(); // 检查取消请求

        // 执行其他操作
        // ...
    }

    return NULL;
}

int main() {
    pthread_t thread;
    int ret;

    // 创建线程
    ret = pthread_create(&thread, NULL, thread_func, NULL);
    if (ret != 0) {
        fprintf(stderr, "Failed to create thread\n");
        exit(EXIT_FAILURE);
    }

    // 等待片刻
    sleep(2);

    // 取消线程
    ret = pthread_cancel(thread);
    if (ret != 0) {
        fprintf(stderr, "Failed to cancel thread\n");
        exit(EXIT_FAILURE);
    }

    // 同步等待线程结束
    ret = pthread_join(thread, NULL);
    if (ret != 0) {
        fprintf(stderr, "Failed to join thread\n");
        exit(EXIT_FAILURE);
    }

    printf("Thread finished\n");

    return 0;
}

文件记录锁示例代码

fcntl

fcntl 记录锁的第三个参数是 flock 结构体

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd;
    struct flock fl;

    // 打开文件
    fd = open("example.txt", O_RDWR);
    if (fd == -1) {
        perror("Failed to open file");
        exit(EXIT_FAILURE);
    }

    // 加锁
    fl.l_type = F_WRLCK;
    fl.l_whence = SEEK_SET;
    fl.l_start = 0;
    fl.l_len = 0;
    if (fcntl(fd, F_SETLK, &fl) == -1) {
        perror("Failed to lock file");
        exit(EXIT_FAILURE);
    }

    // 在加锁状态下对文件进行操作
    printf("File locked\n");
    sleep(5);

    // 解锁
    fl.l_type = F_UNLCK;
    if (fcntl(fd, F_SETLK, &fl) == -1) {
        perror("Failed to unlock file");
        exit(EXIT_FAILURE);
    }

    printf("File unlocked\n");

    // 关闭文件
    close(fd);

    return 0;
}

flock

传统 BSD函数

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd;
    int ret;

    // 打开文件
    fd = open("example.txt", O_RDWR);
    if (fd == -1) {
        perror("Failed to open file");
        exit(EXIT_FAILURE);
    }

    // 加锁
    ret = flock(fd, LOCK_EX);
    if (ret == -1) {
        perror("Failed to lock file");
        exit(EXIT_FAILURE);
    }

    // 在加锁状态下对文件进行操作
    printf("File locked\n");
    sleep(5);

    // 解锁
    ret = flock(fd, LOCK_UN);
    if (ret == -1) {
        perror("Failed to unlock file");
        exit(EXIT_FAILURE);
    }

    printf("File unlocked\n");

    // 关闭文件
    close(fd);

    return 0;
}

lockf

POSIX 锁

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd;
    int ret;

    // 打开文件
    fd = open("example.txt", O_RDWR);
    if (fd == -1) {
        perror("Failed to open file");
        exit(EXIT_FAILURE);
    }

    // 加锁
    ret = lockf(fd, F_LOCK, 0);
    if (ret == -1) {
        perror("Failed to lock file");
        exit(EXIT_FAILURE);
    }

    // 在加锁状态下对文件进行操作
    printf("File locked\n");
    sleep(5);

    // 解锁
    ret = lockf(fd, F_ULOCK, 0);
    if (ret == -1) {
        perror("Failed to unlock file");
        exit(EXIT_FAILURE);
    }

    printf("File unlocked\n");

    // 关闭文件
    close(fd);

    return 0;
}
posted @ 2023-07-13 10:26  韩若明瞳  阅读(27)  评论(0编辑  收藏  举报