1. 进程标志
#include <unistd.h>
#include <sys/types.h>
uid_t getpid(void);
uid_t getppid(void);
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
git_t getegid(void);
struct passed *getpwuid(uid_t uid);
struct passwd {
char *pw_name; /* 登录名称 */
char *pw_passwd; /* 登录口令 */
uid_t pw_uid; /* 用户 ID */
gid_t pw_gid; /* 用户组 ID */
char *pw_gecos; /* 用户的真名 */
char *pw_dir; /* 用户的目录 */
char *pw_shell; /* 用户的 SHELL */
};
#include <pwd.h>;
#include <sys/types.h>;
2. fork
pid_t fork();
当 fork 掉用失败的时候(内存不足或者是用户的最大进程数已到)fork 返回-1.父进程中返回子进程id,子进程返回0。
fork()创建子进程就是父进程的一份拷贝,大部分属性都继承过来,但仍有部分属性不同。
1)子进程继承父进程属性
》真实用户ID和组ID,有效用户ID和组ID。
》进程组ID
》session ID
》所有打开文件及文件的偏移量。
》控制终端
》设置用户ID和设置组ID标记位
》根目录和当前工作目录
》文件默认创建的权限掩码
》可访问的内存区段
》环境变量及其他资源分配
2)子进程独有属性
》进程ID
》运行时间记录,timer等
》父进程对文件的锁定
》exec时子进程的信号处理函数指针组置为空,fork时子进程继承父进程信号处理函数(共用相同代码)
3. wait和waitpid
#include <sys/types.h>;
#include <sys/wait.h>;
pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid,int *stat_loc,int options);
两个系统调用用于等待子进程状态改变,并获取状态信息。如果该进程没有子进程或子进程已经结束,则wait/waitpid就会立即返回。
wait()阻塞执行直到子进程终止。
waitpid()默认情况下仅仅等待终止的子进程。但可通过options指定另外两种子进程状况改变。状态改变包含: the child terminated(默认,options=0); the child was stopped by a signal(options=WUNTRACED); or the child was resumed by a signal (options=WCONTINUED). 在子进程终止情况下,wait/waitpid准许系统释放子进程相关资源;若没有调用wait或waitpid子进程变为僵尸进程。
wait <==> waitpid(-1, &status, 0);
假如chilid已经改变状态,wait/waitpid会立即返回,否则两函数阻塞直到子进程状态改变或两函数被中断。若子进程状态改变,但还没有被wait/waitpid,则该进程被称为waitable。
waitpid()中pid取值范围如下:
< -1 等子进程中进程组ID等于pid绝对值的任一进程。
-1 等任一子进程。
0 等进程组ID等于调用进程组ID的任一进程。
> 0 等指定pid的状态改变。
waitpid()的options为0或(|)下列值:
WNOHANG,父进程不阻塞直接返回。
WUNTRACED,假如一个子进程停止(不能通过ptrace跟踪)就返回。若此选项不指定,则可追踪(traced)的已停止子进程的状态被返回。
WCONTINUED,一个已停止的进程由于收到信号SIGCONT而恢复则返回。
stat_loc保存子进程退出状态(一般exit,_exit,return),是一个整型指针,存储退出状态。若stat_loc不为NULL,则wait/waitpid储存退出状态(int型)在stat_loc中。可通过下列宏判断,参数为整型数,非指针。
WIFEXITED(status) return true假如子进程正常终止(exit,_exit, return).
》》WEXITSTATUS(status) 返回子进程的退出状态,其由status的最低8bits组成。仅当WIFEXITED返回真时有效。
WIFSIGNALED(status) 返回真,假如子进程被信号终止。
》》WTERMSIG(status) 返回信号数字值(造成子进程终止的信号),仅当WIFSIGNALED为真时有效。
》》WCOREDUMP(status) 返回真,假如子进程生成core dump。仅当WIFSIGNALED为真时有效。
WIFSTOPPED(status) 返回真,假如子进程被信号stopped。仅当options包含WUNTRACED或子进程正在被traced时有效。
》》WSTOPSIG(status) 返回信号数字值(造成子进程stop的信号)。仅当WIFSTOPPED为真时有效。
WIFCONTINUED(status) 返回真,假如子进程被SIGCONT恢复。
RETURN VALUE
wait(): on success, returns the process ID of the terminated child; on error, -1 is returned
waitpid(): on success, returns the process ID of the child whose state has changed; if WNOHANG was specified and one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is returned.On error, -1 is returned.
4. SIGCHLD
SIGCHLD信号产生的条件
1.子进程终止时会向父进程发送SIGCHLD信号,告知父进程回收自己,但该信号的默认处理动作为忽略,因此父进程仍然不会去回收子进程,需要捕捉处理实现子进程的回收;
(注意:需要注意的是,虽然进程对于 `SIGCHLD`的默认动作是忽略,但是还是显示写出来,才能有效;signal(SIGCHLD, SIG_IGN)
,这样子进程直接会退出。)
2.子进程接收到SIGSTOP(19)信号停止时;
3.子进程处在停止态,接受到SIGCONT后唤醒时。
综上:子进程结束、接收到SIGSTOP停止(挂起)和接收到SIGCONT唤醒时都会向父进程发送SIGCHLD信号。父进程可以捕捉该信号,来实现对子进程的回收,或者了解子进程所处的状态。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void sys_err(char *str)
{
perror(str);
exit(1);
}
void do_sig_child(int signo)
{
int status;
pid_t pid;
// 不可以将捕捉函数内部的while替换为if。因为在执行捕捉函数期间,发送了多次SIGCHLD信号,未决信号集只是记录了一次
// 因此下一次再调用捕捉函数时,if只能完成对一个子进程的回收(即使有多个子进程都发了信号,但是只是调用一次捕捉函数)。
// 而while循环则可以对所有结束了的子进程都完成回收。因此对于多个子进程的回收,最好采用循环的方式,不采用if。
// if ((pid = waitpid(0, &status, WNOHANG)) > 0) {
while ((pid = waitpid(0, &status, WNOHANG)) > 0) {
if (WIFEXITED(status))
printf("------------child %d exit with %d\n", pid, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("child %d killed by the %dth signal\n", pid, WTERMSIG(status));
}
}
int main(void)
{
pid_t pid;
int i;
//阻塞SIGCHLD
for (i = 0; i < 10; i++) {
if ((pid = fork()) == 0)
break;
else if (pid < 0)
sys_err("fork");
}
if (pid == 0) { //10个子进程
int n = 1;
while (n--) {
printf("child ID %d\n", getpid());
sleep(1);
}
return i+1; //子进程结束状态依次为1、2、••••••、10
} else if (pid > 0) {
struct sigaction act;
act.sa_handler = do_sig_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, NULL);
//解除对SIGCHLD的阻塞
while (1) {
printf("Parent ID %d\n", getpid());
sleep(1);
}
}
return 0;
}
父子进程信号处理:
1.子进程继承了父进程的信号屏蔽字和信号处理动作,但子进程没有继承未决信号集spending。
2.注意注册信号捕捉函数的位置。
3.应该在fork之前,阻塞SIGCHLD信号。注册完捕捉函数后解除阻塞。
参考:SIGCHLD信号 Linux: 关于 SIGCHLD 的更多细节
5.进程信号
访问共享资源时,进程与信号处理函数存在竞态,所以要互斥访问。同样也会造成死锁。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
pthread_mutex_t mmutex = PTHREAD_MUTEX_INITIALIZER;
void signal_handler(int signo)
{
printf("signal...\n");
pthread_mutex_lock(&mmutex);
int i;
for(i = 0; i < 5; i++){
sleep(1);
printf("signal run [%d]!\n", i);
}
pthread_mutex_unlock(&mmutex);
}
int main()
{
int i;
signal(SIGUSR1, signal_handler);
pthread_mutex_lock(&mmutex);
for(i = 0; i < 5; i++){
sleep(1);
printf("main run [%d]!\n", i);
if(i == 2){
raise(SIGUSR1);
}
}
pthread_mutex_unlock(&mmutex);
pthread_mutex_destroy(&mmutex);
printf("main exit [%d]!\n", i);
return 0;
}
信号处理函数编程注意
在用sigaction函数登记的信号处理函数中可以做的处理是被严格限定的,仅仅允许做下面的三种处理:
1. 局部变量的相关处理
2. “volatile sig_atomic_t”类型的全局变量的相关操作
3. 调用异步信号安全的相关函数
以外的其他处理不要做!若要使用其他请慎重考虑!
6. 僵尸进程
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
僵尸进程危害
1)占用资源(少量,进程描述符task_struct存在,进程占用的资源被回收,不存在内存泄漏,实际上基本不浪费系统资源,参宋宝华的课程);
2)难以清除(杀死僵尸进程父进程)。
成为僵尸进程的因素
1. 子进程 先于 父进程退出;
2. 子进程的状态信息,没有被父进程回收;
那么问题来了,子进程退出了,父进程怎么知道呢?
对该机制有稍微了解的话,不难得知一个关键因素:SIGCHLD
。正是这个SIGCHLD
起到了通知的作用,所以后面的处理也是基于它而实现。
僵尸进程处理方案
1. 父进程调用wait()或者waitpid()等待子进程结束,这样处理父进程一般会阻塞在wait处而不能处理其他事情。
2. 捕捉SIGCHLD信号,并在信号处理函数里面调用wait函数,这样处理可避免1中描述的问题。
3. 父进程直接忽略该信号。signal(SIGCHLD, SIG_IGN)
,这样子进程直接会退出。 需要注意的是,虽然进程对于 `SIGCHLD`的默认动作是忽略,但是还是显示写出来,才能有效(不显示写出来无效);
4. 把父进程杀了,子进程直接过继给 init
,由 init
伺候着。 不用担心 init
会挂着一堆僵尸, init
本身的设计就有专门回收的处理,所以有多少回收多少。实现:fork两次,父进程创建儿子进程,儿子进程再创建一个孙子进程,然后儿子进程自杀,孙子进程成为孤儿进程被init进程收养。
事后处理(杀父进程)
方法一: kill –18 PPID (PPID是其父进程)
这个信号是告诉父进程,该子进程已经死亡了,请收回分配给他的资源。
方法二:如果不行则看能否终止其父进程(如果其父进程不需要的话)。先看其父进程又无其他子进程,如果有,可能需要先kill其他子进程,也就是兄弟进程。方法是:
kill –15 PID1 PID2(PID1,PID2是僵尸进程的父进程的其它子进程)。
然后再kill父进程:kill –15 PPID
Z之所以杀不死,是因为它已经死了,否则怎么叫 Zombie(僵尸)呢?冤魂不散,自然是生前有结未解之故。
在UNIX/Linux中,每个进程都有一个父进程,进程号叫PID(Process ID),相应地,父进程号就叫PPID(Parent PID)。当进程死亡时,它会自动关闭已打开的文件,舍弃已占用的内存、交换空间等等系统资源,然后向其父进程返回一个退出状态值,报告死讯。如果程序有 bug,就会在这最后一步出问题。儿子说我死了,老子却没听见,没有及时收棺入殓,儿子便成了僵尸。在UNIX/Linux中消灭僵尸的手段比较残忍,执行 ps axjf 找出僵尸进程的父进程号(PPID,第一列),先杀其父,然后再由进程天子 init(其PID为1,PPID为0)来一起收拾父子僵尸,超度亡魂,往生极乐。
注意,子进程变成僵尸只是碍眼而已,并不碍事,如果僵尸的父进程当前有要务在身,则千万不可贸然杀之。