Linux 进程的基本控制—atexit、on_exit函数、文件锁

本文介绍了Linux系统中进程的基本控制,包括atexit和on_exit函数,详细阐述了这两个函数的使用及调用顺序。同时,文章还探讨了文件锁的概念和实现,通过示例代码展示了如何使用fcntl进行文件加锁和解锁,并讨论了在多进程环境下文件锁的重要性。

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

进程的基本控制:

     

进程控制函数:pause、sleep、usleep

 

函数atexit  on_exit

 

atexit函数:

    #include<stdlib.h>

     功能:注册终止函数(即进程执行结束后调用的函数)

     用法:int atexit( void (*func)(void)); (参数为回调函数)

     注意:按照ISO C的规定,一个进程可以登记多达32个函数,这些函数将由exit自动调用。atexit()注册的函数类型应为不接受任何参数的void函数,exit调用这些注册函数的顺序与它们登记时候的顺序相反。同一个函数如若登记多次,则也会被调用多次

 

例子:

 

#include<stdio.h>

#include<stdlib.h>

 

voidfunc()

{

       printf(“over\n”);

}

 

voidmain()

{

      atexit(func);

      printf(“进程:\n”);

 

}

 

运行结果:

进程:

over

 

说明:在进程结束时,会自动调用atexit函数的参数的回调函数,我们可以在这个回调函数中做一些操作,比如释放一些资源。

 

例子2:

 

 

 

#include<stdio.h>

#include<stdlib.h>

 

voidfun1()

{

    printf(“fun1被调用!\n”);

}

 

voidfun2()

{

    printf(“fun2被调用!\n”);

}

 

voidfun3()

{

    printf(“fun3被调用!\n”);

}

 

intmain()

{

atexit(fun1);

atexit(fun2);

atexit(fun3);

printf(“进程结束!\n”);

 

 

}

 

运行结果:

进程结束!

fun3被调用!

fun2被调用!

fun1被调用!

 

说明:进程结束所调用的回调函数,与其注册的顺序相反。

 

on_exit函数:

on_exit()函数式atexit函数的扩展

表头文件 #include<stdlib.h>   

定义函数 int on_exit(void (* function)(int, void*),void *arg);

 

参数arg 指针会传给参数function函数

function 的第一个参数是进程结束的返回码,第二个void指针参数为从on_exit函数中的参数arg传过来的。

返回值 如果执行成功则返回0,否则返回-1,失败原因存于errno中  

 

 

例子:

 #include<stdlib.h>
  void my_exit(int status,void *arg)
  {
         printf(“before exit()!\n”);
         printf(“exit (%d)\n”,status);
         printf(“arg = %s\n”,(char*)arg);

}

main()
{
 char * str=”test”;
  on_exit(my_exit,(void*)str);

exit(12);

}
执行
before exit()!
exit (12)

arg = test

 

 

 

进程与文件锁:

      在多进程下文件读写是共享的

       问题:

             怎么知道一个文件正在被另一个进程读写?

       解决方案:

              文件锁(建议锁)

       API函数:

                fcntl(文件锁受内核参数影响)

       编程技巧:

                对文件加锁

                判断一个文件是否存在锁

       函数说明:

                int fcntl(

                       int fd,//被加锁的文件描述符

                       int cnd,//加锁方式:F_SETLK(已经加锁则返回异常) F_SETLKW(已经加锁,堵塞等在,直到它减锁)F_UNLK

struct flock *lk);//锁的描述

                返回值:

                       0:加锁成功

                      -1:加锁失败

 

结构体flock的指针:
struct flcok 

short int l_type; /* 锁定的状态*/

//以下的三个参数用于分段对文件加锁,若对整个文件加锁,则:l_whence=SEEK_SET, l_start=0, l_len=0
short int l_whence; /*决定l_start位置*/ 
off_t l_start; /*锁定区域的开头位置*/ 
off_t l_len; /*锁定区域的大小*/

pid_t l_pid; /*锁定动作的进程*/ 
};

l_type 有三种状态: 
F_RDLCK   建立一个供读取用的锁定 
F_WRLCK   建立一个供写入用的锁定 
F_UNLCK   删除之前建立的锁定

_whence 也有三种方式: 
SEEK_SET   以文件开头为锁定的起始位置 
SEEK_CUR   以目前文件读写位置为锁定的起始位置 
SEEK_END   以文件结尾为锁定的起始位置

 

 

 

案例:

写两个程序:

                            A:加锁

                            B:获取锁的信息

 

1.       加锁程序

Setlock.c

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<fcntl.h>

 

void  main()

{

      int fd;

      struct flock  lk;

      int r;

      //打开文件

      fd=open(“text.txt”,O_RDWR);

     if(fd==0)

     {

           printf(“open error:%m\n”);

           exit(-1);

     }

     //描述锁

lk.l_type=F_RDWR;

lk.l_whence=SEEK_SET;

lk.l_start=5;

lk.l_len=10;

//加锁

r=fcntl(fd,F_SETLK,&lk);

if(r==0)

    printf(“加锁成功!\n”);

else

    printf(“加锁失败!\n”);

 

while(1);   //加个死循环为使进程不退出,程序退出后自动减锁

}

 

2.       获取锁的信息:

Getlock.c

 

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<fcntl.h>

 

 

void main()

{

int fd;

      struct flock  lk;

      int r;

      //打开文件

      fd=open(“text.txt”,O_RDWR);

     if(fd==0)

     {

           printf(“open error:%m\n”);

           exit(-1);

     }

     //获取锁的信息:

  r=fcntl(fd,F_GETLK,&lk);

  if(r==0)

{

    printf(“得到锁成功!\n”);

   if(lk.l_type==F_RDWR)

    {

         printf(“写锁\n”);

    }

}

else

{

    printf(“得到锁失败!\n”);

}

 

 

 

}

 

结论:锁也是一个进程共享的信息

   

<think>好的,我现在需要回答关于atexit函数的问题。首先,我需要确认用户的需求。用户可能是在编程过程中遇到了需要处理退出时清理资源的情况,所以想知道atexit的用法和注意事项。 首先,回顾atexit函数基本知识。atexit是C标准库中的一个函数,用于注册在程序正常终止时调用的函数。这些注册的函数通常是用来做清理工作的,比如关闭文件、释放内存等。我需要解释它的基本用法,比如如何声明和注册函数。 然后,我需要考虑函数的参数和返回值。atexit接受一个函数指针,该函数没有参数也没有返回值。返回值方面,如果注册成功返回0,失败返回非零值。这一点需要明确说明,避免用户在使用时忽略错误检查。 接下来,思考用户可能的疑问。比如,注册多个函数时的执行顺序,是否按照注册的相反顺序执行?是的,后注册的先执行,类似栈的结构。需要举例说明,比如先注册func1,再注册func2,退出时先执行func2,再执行func1。这样用户能更好地理解执行顺序。 另外,需要注意atexit的局限性。比如,它只处理正常退出情况,如main函数返回或exit调用,而不处理abort或信号导致的终止。用户可能不知道这些细节,需要特别指出,避免他们在异常情况下依赖atexit导致资源泄漏。 还要提到多线程环境下的使用情况。虽然C11标准支持多线程,但不同编译器可能有不同实现,需要注意线程安全问题。如果用户在多线程程序中注册atexit函数,需要确保注册过程是线程安全的,避免竞争条件。 可能用户会问,注册的函数是否有数量限制?根据C标准,至少支持32个注册函数,但实际可能更多,不过依赖具体实现。建议用户不要过度依赖这个限制,尽量控制注册数量。 另外,需要举例说明代码结构。例如,如何在main函数中注册多个清理函数,并展示它们的执行顺序。这样用户能更直观地理解用法。同时,指出在C++中,如果有析构函数,可能不需要大量使用atexit,但有时仍有必要,比如全局资源的清理。 可能用户会混淆atexiton_exit函数。虽然on_exit在某些系统中存在,但不是标准C函数,可移植性差。因此,建议优先使用atexit。 最后,总结atexit的适用场景和注意事项,帮助用户正确使用该函数,避免常见错误。比如,确保注册的函数不会抛出异常,尤其是在C++中,否则可能导致未定义行为。 检查是否有遗漏点,比如参数类型是否正确,函数声明是否符合要求,是否需要使用特定的头文件(stdlib.h)。这些都是需要注意的细节,用户可能在实现时遇到问题,需要提前说明。 另外,提醒用户如果在注册的函数中调用exit可能导致无限递归,导致栈溢出,因此需要避免这种情况。同时,atexit注册的函数应尽快完成,避免延迟程序终止。 综合以上思考,组织一个结构清晰的回答,分步骤解释atexit的功能、用法、示例、注意事项等,确保用户能够全面理解并正确应用该函数。</think>--- ### 什么是`atexit`函数? `atexit` 是C/C++标准库中用于**注册程序退出时执行的清理函数**的接口。它允许开发者注册多个函数(通常称为**退出处理程序**),这些函数会在程序正常终止时(例如通过`main`函数返回或调用`exit()`)按**后注册先执行**的顺序自动调用。 --- ### 函数原型 ```c #include <stdlib.h> int atexit(void (*func)(void)); // 参数:无参数、无返回值的函数指针 ``` - **返回值**: - 成功:返回`0` - 失败:返回非零值(例如注册函数数量超过系统限制) --- ### 关键特性 1. **执行顺序**:遵循后进先出(LIFO)原则 ```c atexit(func1); // 注册顺序1 atexit(func2); // 注册顺序2 // 退出时执行顺序:func2 → func1 ``` 2. **触发条件**: - `main()`函数正常返回 - 调用`exit()`函数 - **不触发**的情况: - 程序调用`abort()` - 进程被信号强制终止(如`SIGKILL`) 3. **注册限制**: - C标准规定至少支持`32`个注册函数 - 实际数量取决于编译器实现(可通过`sysconf(_SC_ATEXIT_MAX)`查询) --- ### 使用示例 ```c #include <stdio.h> #include <stdlib.h> void cleanup1() { printf("清理资源1:关闭文件\n"); } void cleanup2() { printf("清理资源2:释放内存\n"); } int main() { atexit(cleanup1); // 第1个注册 atexit(cleanup2); // 第2个注册 printf("主程序运行中...\n"); return 0; } ``` **输出结果**: ``` 主程序运行中... 清理资源2:释放内存 清理资源1:关闭文件 ``` --- ### 注意事项 1. **多线程环境**: - 在C11标准下,`atexit`是线程安全的 - 但需确保注册时不会发生资源竞争(如不同线程注册相同函数) 2. **信号安全**: - 退出处理函数中**不能**调用非异步信号安全函数(如`printf`) 3. **C++的额外机制**: - 对于对象析构,优先使用类的析构函数 - 全局/静态对象的析构函数调用**早于**`atexit`注册的函数 4. **禁止操作**: - 在退出处理函数中再次调用`exit()`(会导致无限递归) - 注册已注册过的函数(可能引发未定义行为) --- ### 典型应用场景 1. 关闭数据库连接池 2. 持久化未保存的缓存数据 3. 删除临时文件 4. 释放全局共享资源锁 --- ### 替代方案 | 方案 | 适用场景 | 特点 | |---------------------|----------------------------|-----------------------------| | `atexit` | 跨平台标准清理 | 简单易用,但功能有限 | | `on_exit` (非标准) | Linux特定扩展 | 可获取退出状态码 | | 信号处理(如SIGTERM)| 处理异常终止 | 需要处理异步安全问题 | 建议优先使用`atexit`实现标准清理逻辑,结合其他机制处理异常终止情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值