APUE 线程私有数据与errno的实现

线程私有数据

每个线程可以独立地访问数据副本,而不需要担心与其他线程的同步访问问题。

它提供了让基于进程的接口适应多线程环境的机制,一个很明显的实例就是errno。为了让线程也能够使用那些原本基于进程的系统调用和库例程,errno被重新定义为线程私有数据。这样,一个线程设置errno的操作并不会影响进程中其他线程的errno值。

 

#include<pthread.h>

int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));

“键”可以被进程中的所有线程使用,而每个线程把这个键与不同的线程私有数据地址进行关联,这就是其基本原理。

 

errno的定义

 

在<errno.h>头文件中:

/* Declare the `errno' variable, unless it's defined as a macro by
   bits/errno.h.  This is the case in GNU, where it is a per-thread
   variable.  This redeclaration using the macro still works, but it
   will be a function declaration without a prototype and may trigger
   a -Wstrict-prototypes warning.  */

#ifndef errno
extern int errno;
#endif

在<bits/errno.h>头文件中:

# ifndef __ASSEMBLER__
/* Function to get address of global `errno' variable.  */
extern int *__errno_location (void) __THROW __attribute__ ((__const__));

#  if !defined _LIBC || defined _LIBC_REENTRANT
/* When using threads, errno is a per-thread value.  */
#   define errno (*__errno_location ())
#  endif
# endif /* !__ASSEMBLER__ */


errno实际上,并不是我们通常认为的是个整型数值,而是通过整型指针来获取值的。这个整型就是线程安全的。

另外,宏之所以这样实现,是因为标准库规定了必须能够通过&errno方式取得保存错误代码的变量的地址,因此__errno_location()函数的返回值是指针,并把宏定义为解引用函数返回的地址*__errno_location()。如果__errno_location直接返回int类型,此时就无法取得保存错误代码的变量的地址。

 

errno的可能实现

 

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

static pthread_key_t key;
static pthread_once_t init_done = PTHREAD_ONCE_INIT;

static void
thread_init(void)
{
        pthread_key_create(&key, free);
}

int *
__errno_location_simply_fake(void)
{
        int *errp;

        /* 保证多线程环境里 key 只被创建一次 */
        pthread_once(&init_done, thread_init);

        errp = (int *)pthread_getspecific(key);
        if(errp == NULL) {
                errp = malloc(sizeof(int));
                if(errp != NULL)
                        pthread_setspecific(key, errp);
        }

        return errp;
}

 

参考:

1. 《APUE》第12.6节 线程私有数据

2. blog.csdn.net/romandion/archive/2008/01/11/2036975.aspx

3. bbs.chinaunix.net/viewthread.php

 

僵死进程

APUE 第8章:

如果子进程在父进程之前终止,那么父进程又如何能在做相应检查时得到子进程的终止状态呢?

对此问题的回答是:内核为每个终止子进程保存了一定量的信息,所以当父进程调用wait或者waitpid时,可以得到这些信息,这些信息至少包括进程ID,终止状态,以及该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储区,关闭其所有打开的流等。

一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息等)的进程被称为僵死进程(zombie)

 

想到的一个问题就是:终止子进程处于僵死进程直到什么时候为止?

于是google之:

来源: blogs.sun.com/haifeng/entry/unix_%E5%83%B5%E6%AD%BB_zombie_%E8%BF%9B%E7%A8%8B1

进程在它的生命周期有几种状态:睡眠,可运行,停止,正在运行和僵死状态。 所谓僵死进程,指的是一个进程已经退出,它的内存和相关的资源已经被内核释放掉,但是在进程表中这个进程项(entry)还保留着,以便它的父进程得到它 的退出状态。 一个进程退出时,它的父进程会收到一个SIGCHLD信号。一般情况下,这个信号的句柄通常执行wait系统调用,这样处于僵死状态的进程会被删除。 如果父进程没有这么做,结果是什么呢?毫无疑问,进程会处于僵死状态。 实际上,僵死进程不会对系统有太大的伤害,最多就是它的进程号(PID)和进程表中的进程项系统不能使用。(感觉这里说的更加清楚,难道是中文版翻译的不行呵呵)

去掉僵尸进程

在Solaris中,可以用ptree命令列出所有进程,查找进程名称为“defunct”的进程。如果发现了,那么系统存在僵死进程。 注意用kill命令不能杀死这种进程。原因是它已经退出了,什么也没有了,自然无法收到任何信号。

删除僵尸进程的根本方法是让它的父进程调用wait来处理SIGCHLD信号。有两种方式可以做到这一点。一种方法是改变它的父进程。可以用kill命令 杀死它的父进程,这样init变成它的新的父进程,而init会定时地执行wait系统调用。另一种方式是使用调试器,在父进程中执行wait系统调用。 如果知道了父进程的程序名称和它的进程号,运行下面命令:

   %gdb `parent application name` `parent pid`
   (gdb) set unwindonsignal on
   (gdb) call wait(pid of zombie process)

gdb会在父进程中调用wait,从而达到我们的目的。注意,unwindonsignal要被set为on, 它告诉gdb把堆栈恢复到调用wait之前的状态。要不然父进程会crash。 在程序中避免僵死进程 除了显式调用wait或waitpid外,也可以使用下面的代码来避免僵死进程(这儿假设父进程对子进程的状态不感兴趣),它遵循POSIX,是可移植 的。

   struct sigaction sa;
   sa.sa_handler = SIG_IGN;
   sa.sa_flags = SA_NOCLDWAIT;
   sigemptyset (&sa.sa_mask);
   sigaction (SIGCHLD, &sa, NULL);

参考exit(2)手册页。

 

来源: www.chinaunix.net/jh/4/665906.html

一、僵屍進程的産生

当子进程比父进程先运行结束,而父进程没有回收子进程的时候,子进程将成为一个僵尸进程。如果父进程先退出,子进程被init接管,子进程退出后init会回收,就没事了。

二、僵屍進程的危害

僵尸进程是一个运行完毕的进程,所有资源都已经释放了,除了它的进程表项。因此,导致的影响如下:如果操作系统最多能管理1000个进程,那么僵尸进程的存在,将会使得操作系统管理正常进程减少。

三、如何避免僵屍進程的産生

1、父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起

2. 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收

3. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号

4. 还有一些技巧,就是fork两次(APUE有介绍),父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收估计还要自己做。

终于把APUE看完一遍

还是挺有成就感的,虽然只是把这本经典书看了一遍,书中的例子基本敲了一遍。

翻看之前的blog记录,在2月28日有一篇: 学习计划Unix-C,其中写着想看的几本关于Unix下C编程的书,原本打算是今年暑假看完的,没想到现在已经看的差不多了,还剩一本《C陷阱与缺陷》,目前正在看,很薄的一本书,都不敢看得太快,否则没几天又看完了嘻嘻。

当然,还仅限于把书看完的程度而已,与掌握相比还有相当大的距离。可以说,看完第一遍,只是对于一些基本的系统调用与函数有了感性的认识,真正想要掌握和灵活地运用,关键还是在于多实践,光看书却不实践,是没法真正提高水平的。

接下来,准备把APUE再仔细地翻看一遍,第一遍看的时候有很多地方有疑惑,对于某些章节特别是信号以及线程的内容还不能理解透彻,第二遍打算看得仔细点,速度放慢点,另外把每一章的课后习题也好好钻研钻研。

总之,继续努力吧,Fighting...

另外,要说大四有什么好的话,对于我来说,那就是有充足的时间来学习自己感兴趣的东西

我的大四,过得很充实...

ps.

前几天在写下载歌词的小程序时,突然发觉自己挺喜欢编程的呵呵。

想当初,从历史系转过来的时候,只是想着计算机系至少是个理科,并没有想到究竟自己喜不喜欢这个专业,现在看来,挺适合我的。

getaddrinfo函数

 

今天在看完APUE第16章:网络IPC:套接字后,就开始编写书中的示例代码,结果出现了问题:

调用getaddrinfo函数时,返回错误:Servname not supported for ai_socktype,觉得很费解,就直接google之,发现下面这篇好文,不仅解决了问题,还使我更加理解了如何使用getaddrinfo来编写服务器/客户端程序

 

原文地址:blog.csdn.net/andyxie407/archive/2007/06/30/1672325.aspx

有这样一个C/S程序,server提供一个叫做ruptime的服务,功能是当有客户端连接时调用uptime程序,并将结果发送到client。可是现在的问题是,这个服务系统本来是没有的,所以调用getaddrinfo的时候会返回如下错误:

代码:
Servname not supported for ai_socktype

我觉得可能是需要编辑/etc/service文件把自己这个服务加进去.

 

个人认为,这个问题就是对getaddrinfo函数的应用和理解,下面帖子的内容基本上是对Advanced Programming in linux Environment这本书里的16-6等几个程序的解释,刚开始对getaddrinfo这个函数和编辑/etc/service等不了解,所会有以上的问题存在。下面是资料。

bumpy:~/tmp$ gcc a.c
bumpy:~/tmp$ ./a.out <==== 没有改/etc/services 前
getaddrinfo error: Servname not supported for ai_socktype
bumpy:~/tmp$ vi a.c
bumpy:~/tmp$ sudo vi /etc/services <==== 添加ruptimed 4000/tcp到合适的位置(你的服务里没有别的是4000吧)
bumpy:~/tmp$ ./a.out
OK

买了APUE中文版

毕业论文的理论阶段基本结束,这几天等着信息与网络中心提供服务器日志。

人不能闲着阿,是吧?否则不是浪费生命嘛,所以又开始了Unix-C呵呵。

把《Pointers On C》基本看完了,内容很基础,适合初学,加上有C++的基础所以就很快的过了一遍。

 

前天在卓越下单买了《Unix环境高级编程》和《C陷阱与缺陷》,今天中午书就送到了,速度还是很满意的(*^__^*) 嘻嘻……

接下来就准备赶紧把毕设搞定,然后继续我的Unix-C之旅,另外别忘了pku做题。

 

看到好书总有一定要买来的冲动,这不把三本关于C的书都买齐了,艾 ..... 都是money啊

 

另外,打算把大学期间买的一些书在BBS上卖掉咯,但愿还有人要