首先说一下什么是getpw系列函数,它主要是指这些函数:

  

  这些函数根据一个用户名(getpwnam和getpwnam_r两个函数)或者一个用户ID(getpwuid和getpwuid_r)来获取这个用户在/etc/passwd中相应的条目信息,并把这些信息存放在一个struct passwd的结构体里面,然后再把这个结构体的指针返回。问题就出在这个存储用户信息的结构体上面,它是由getpw函数在程序中自定义的一块静态存储区,而且每调用一次getpw函数,这个静态存储区就会被重写一次。比如下面这段代码:

 #include<errno.h>
#include<pwd.h>
#include<string.h>
#include<stdlib.h>
#include<stdarg.h>
#include<stdio.h>
#include<sys/types.h> #define BUFSIZE 512 void err_exit(char *fmt,...); int main(int argc,char *argv[])
{ struct passwd *mistr;
struct passwd *rootstr; if(NULL == (mistr=getpwnam("michael")))
err_exit("[getpwnam]<mi>:");
if(NULL == (rootstr=getpwnam("root")))
err_exit("[getpwnam]<root>:"); printf("name = %s\n",mistr->pw_name);
printf("name = %s\n",rootstr->pw_name); return ;
}

  我想要获取michael用户和root用户的信息,然后我很异想天开得创建了两个指针,一个指向michael用户的信息,一个指向root用户的信息,以为这样就能分别保存两个用户的信息了,然后发现程序的运行结果如下图:

  

  发现两次打印的信息,都是root,说明后面调用的getpwnam函数重写了struct passwd这个结构体,把前面调用getpwnam函数时写入的michael用户的信息给覆盖了。

  这个就是getpwnam_r和getpwuid_r函数出现的原因,为了防止多线程程序因为竞争而产生错误。关于这两个函数getpwnam的文档中是这样描述的:

  

  这两个函数可以将用户信息存放在用户定义的struct passwd结构体中,而不一定非得要放在函数定义的,可能被重写的静态区域中,这样多线程程序运行的时候就可以避免竞争了。

  接下来再来说我的一个新的发现,就是getpw函数调用的时候是有锁的,也就是说同一个进程中,如果一个getpw函数正在运行,另一个getpw函数是无法运行的。

  这个发现是我在运行《APUE》的程序清单10-2的时候发现的,《APUE》上面的代码如下(不是完全相同,自己重写了一下^_^):

 /**
* 可重入函数的概念就是可以在被调用了一半的时候被打断,然后再来从新调用,这里的getpwnam就是一个不可重入函数
* ,因为getpwnam函数会修改一个静态变量来保存读出的用户信息,如果调用了一半再被重新调用,则有可能会让原来的
* 信息被覆盖掉
*
* 现在getpwname函数可能有锁,信号处理函数再来调用这个函数会卡死
*/ #include<errno.h>
#include<pwd.h>
#include<signal.h>
#include<string.h>
#include<stdlib.h>
#include<stdarg.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h> #define BUFSIZE 512 void err_exit(char *fmt,...);
int err_dump(char *fmt,...);
int err_ret(char *fmt,...); /**
* 本函数用来处理SIGALRM信号
*/
void sig_alrm(int signo)
{
struct passwd *rootstr; printf("In signal SIGALRM handler\n"); if(NULL == (rootstr=getpwnam("root")))
err_exit("[getpwnam]<root>:");
else
printf("pw_name = %s\n",rootstr->pw_name); alarm();
} int main(int argc,char *argv[])
{ struct passwd *mistr;
signal(SIGALRM,sig_alrm); /**
* alarm函数用来在1秒钟之后发送一个SIGALRM信号给本程序
*/
alarm(); for(;;)
{
if(NULL == (mistr=getpwnam("michael")))
err_exit("[getpwnam]<mi>:");
if(strcmp(mistr->pw_name,"michael"))
printf("结果错误,读出的用户名是:%s\n",mistr->pw_name);
} return ;
}

  这个程序的流程是主函数调用getpwname获取michael用户的信息,同时通过alarm函数给自己发送SIGALRM信号,在SIGALRM的信号处理函数中,我们再来调用getpwnam函数获取root用户的信息,作者想要的结果是主函数不断读取michael用户的信息,但是SIGALRM信号处理函数获取了root用户的信息,所以最后主函数最后打印就有可能是root用户的信息,此时主函数就打印读取出错。

  但是在程序运行的过程中我的电脑上却会发生死锁的情况,运行结果如下图所示:

  

  到此时程序就卡死了,不会再继续下去了。

  后来我又用gdb把这个程序调试了一下,首先在32行那里增加一个断点,然后再用si命令一步一步地执行汇编指令,执行的过程如下:

  

  si命令在调试时是会进入函数内部的,所以我在进入getpwnam函数之后又一步一步地深入,最后我发现我的函数卡在了__lll_lock_wait_private()这个地方,函数的调用栈如下:

  

  前4行表示接受到了信号之后的处理过程,最后函数卡在了__lll_lock_wait_private()这个函数这里。我google了一下这个函数,在这里找到了它的实现。这个有点惭愧,看了半天愣是没看懂那个实现代码是是什么意思,不过我在那里看到了那个文件是一个线程库,所以我猜getpwnam函数的执行过程应该是这样的:

  在getpwnam函数内部,具体还是要调用getpwnam_r函数来实现的(从上面函数栈截图的5~8行可以看出来),在getpwnam函数内部调用getpwnam_r函数的时候,先要上一个线程锁,调用完成之后,再来解锁这个线程锁,这样来确保getpwnam函数每次只会被一个线程调用。

  而在上面的代码中,主函数中的getpwnam函数没有解锁,就被暂停运行了,而信号处理函数(sig_alrm)函数中的getpwnam再来调用的时候,就会等待主函数中的getpwnam解锁,而主函数又是暂停的,这样就会产生死锁了!

  OK,这就是我的分析了, 后面那个线程锁那里我感觉还不是太了解,讲的不是太好,还望见谅,如果哪位关于这部分有更深的了解,还望不吝赐教!

关于getpw系列函数返回的静态区域的更多相关文章

  1. 【转载】Java系列笔记(3) - Java 内存区域和GC机制

    Java系列笔记(3) - Java 内存区域和GC机制 转载:原文地址http://www.cnblogs.com/zhguang/p/3257367.html 目录 Java垃圾回收概况 Java ...

  2. PHP进程通信基础——shmop 、sem系列函数使用

    PHP进程通信基础--shmop .sem系列函数使用 进程通信的原理就是在系统中开辟出一个共享区域,不管是管道也好,还是共享内存,都是这个原理.如果心中有了这个概念,就会很方便去理解代码.由于官网上 ...

  3. GetLastError()函数返回值及含义

    GetLastError返回的值通过在api函数中调用SetLastError或SetLastErrorEx设置.函数并无必要设置上一次错误信息,所以即使一次GetLastError调用返回的是零值, ...

  4. PHP ob系列函数详解

    一. 相关函数简介:    1.Flush:刷新缓冲区的内容,输出.    函数格式:flush()    说明:这个函数经常使用,效率很高.    2.ob_start :打开输出缓冲区    函数 ...

  5. php Output Control 函数 ob_系列函数详解

    <?php /* * 输出缓冲控制 * * flush — 刷新输出缓冲 ob_clean — 清空(擦掉)输出缓冲区 ob_end_clean — 清空(擦除)缓冲区并关闭输出缓冲 ob_en ...

  6. PHP中ob系列函数整理

    ob,输出缓冲区,是output buffering的简称,而不是output cache.ob用对了,是能对速度有一定的帮助,但是盲目的加上ob函数,只会增加CPU额外的负担. 下面我说说ob的基本 ...

  7. Mem系列函数介绍及案例实现

      昨天导师甩给我们一个项目案例,让我们自己去看一看熟悉一下项目内容,我看到了这个项目里面大量使用memset(sBuf,0,sizeof(sBuf));这一块内存填充的代码,于是回想起以前查过Mem ...

  8. PHP输出缓存ob系列函数

    ob,输出缓冲区,是output buffering的简称,而不是output cache.ob用对了,是能对速度有一定的帮助,但是盲目的加上ob函数,只会增加CPU额外的负担. ob的基本原则:如果 ...

  9. PHP输出缓存ob系列函数详解

    PHP输出缓存ob系列函数详解 ob,输出缓冲区,是output buffering的简称,而不是output cache.ob用对了,是能对速度有一定的帮助,但是盲目的加上ob函数,只会增加CPU额 ...

随机推荐

  1. 代码片段 - JavaScript 字符串模板

    /* ------------------------------ // 字符串模板1,语法严格,不能混用,效率相对较高 // 使用 {{ }} 作为标记是为了允许在模板中使用 JSON 字符串 // ...

  2. iOS “请在微信客户端打开链接” UIWebview加载H5页面携带session、cookie、User-Agent信息 设置cookie、清除cookie、设置User-Agent

    公司新开的一个项目..内容基本上是加载H5页面显示..当时觉得挺简单的..后来发现自己掉坑里了..一些心理历程就不说了..说这个项目主要用到的知识点吧..也是自己踩得坑. 首先说说..这个项目上的内容 ...

  3. PHP.12-PHP-设计文件上传类

    设计文件上传类 [PHP参数详解]{文件上传} ********************** *#构造方法编写 ********************** 此种传参方法规定必须用户必须按响应位置输入 ...

  4. Windows配置端口转发

    windows命令行下用netsh实现端口转发 微软Windows的netsh是一个命令行脚本实用工具.使用netsh工具 ,可以查看或更改本地计算机或远程计算机的网络配置.不仅可以在本地计算机上运行 ...

  5. nodejs的mysql模块学习(九)连接池集群

    连接池集群 连接池集群可以提供多个主机连接 创建连接池集群 //创建连接池集群 var poolCluster = mysql.createPoolCluster(); //添加配置 config是一 ...

  6. [UML]UML之开篇

    前言 大学时,学习软件工程时,学到了UML,由于当时接触项目太少,认识不清,再加上毕业后一直忙于coding,很少有时间去真正的认识和学习UML. 现在感觉有必要去回头看看这些东西啦. 什么是UML ...

  7. Java Lock

    JVM中的另一种锁Lock的实现.与synchronized不同的是,Lock完全用Java写成,在java这个层面是无关JVM实现的.在java.util.concurrent.locks包中有很多 ...

  8. 《UNIX环境高级编程》学习心得 三

    输入和输出 1.文件描述符 文件描述符(file descriptor)可以理解为内核在某个进程中访问一个文件时,内核向进程返回的一个非负整数.在内核使用open或creat打开一个现有文件或者创建一 ...

  9. hdu-5587 Array(回溯)

    题目链接: Array Time Limit: 2000/1000 MS (Java/Others)     Memory Limit: 131072/131072 K (Java/Others) P ...

  10. ado.net 基础(一)

    // C#操作数据库的基础1 引用两个命名空间using data:using date.sqlclient;2 创建与数据库的连接方法一: sqlconnection a = new sqlconn ...