jiayi Rss

Linux 作业控制

Posted by jiayi | Posted in APUE | Posted on 07-01-2009

3

话说一个支持作业控制的 Linux 终端下有 作业1,我们把他放到后台,终端执行 kill %1 ,恩,我们知道它把 作业1 杀掉了。但它只杀掉了 作业1 吗?那可不一定……
首先,作业(job) 指的是 进程组(process group),一个作业就是一个进程组。进程组 是什么,A process group is a collection of one or more processes, usually associated with the same job,that can receive signals from the same terminal。明白了两者各自的定义,很容易看到他们是相通的。把进程组抽象成作业,一个主要目的是便于操控。因此作业控制可以看作进程组控制,只不过更简便了,但作业控制有时却不能按照我们的意愿工作。

下面给出一段程序加以说明
第一个程序命名 job_control.c,第二个程序命名 job_control_1.c

CODE

/* job_control.c */
#include<sys/types.h>
#include<unistd.h>

int main()
{
    if((pid=fork())==0) {
        /* setpgid(0,0); */
        while(1) {
        }
    } else {
        /* setpgid(pid,0); */
        while(1);
    }  
}

CODE

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<sys/types.h>
#include<unistd.h>

int main(int argc,char **argv)
{
    if(argc!=2) {
        printf("Usage: ./job_control_1 <pgid>
"
);
        exit(1);
    }  

    pid_t new_gpid;
    sscanf(argv[1],"%d",&new_gpid);

    if(setpgid(0,new_gpid)!=0) {
        perror("setpgid() error");
        exit(2);
    }  
  
    while(1) {
    }  
}

第一个程序只是简单的fork()一个子进程,第二个程序会将自己的 Group ID 设成 argv[1]

后台分别运行 job_control  job_control _1

jiayi:/home/jiayi/apue # ./job_control &
[1] 7161
jiayi:/home/jiayi/apue # ps -o pid,ppid,pgrp,sid,tpgid,command
  PID  PPID  PGRP   SID TPGID COMMAND
 6631  4167  6631  6631  7163 bash
 7161  6631  7161  6631  7163 ./job_control
 7162  7161  7161  6631  7163 ./job_control
 7163  6631  7163  6631  7163 ps -o pid,ppid,pgrp,sid,tpgid,command
jiayi:/home/jiayi/apue # ./job_control_1 7161 &
[2] 7165
jiayi:/home/jiayi/apue # ps -o pid,ppid,pgrp,sid,tpgid,command
  PID  PPID  PGRP   SID TPGID COMMAND
 6631  4167  6631  6631  7167 bash
 7161  6631  7161  6631  7167 ./job_control
 7162  7161  7161  6631  7167 ./job_control
 7165  6631  7161  6631  7167 ./job_control_1 7161
 7167  6631  7167  6631  7167 ps -o pid,ppid,pgrp,sid,tpgid,command
jiayi:/home/jiayi/apue # kill %1
jiayi:/home/jiayi/apue #
[1]-  已终止               ./job_control
[2]+  已终止               ./job_control_1 7161

从上面的输出可以看出
(1) 终端将它的子进程放进不同的 进程组,而不是放到终端的 进程组
(2) ./job_control 被标记为 job[1]job_control_1 7161 被标记为 job[2] ,但他们的 进程组 同为 7161
(3) kill %1 不仅杀掉 job[1],同时将 job[2] 杀掉

估计没哪个程序会这样写,但从中可以看出 作业控制 确实没有将 进程组 规划好。正确的做法应该是,将 job_control_1 7161 归入 job[1],并给用户打印一条说明。

在此折腾过程中,还发现一个有意思的现象,将 ./job_control_1 7161 放到前台来执行,你将失去对终端的控制权,面对终端,无法操作!
下面演示了这个过程

jiayi:/home/jiayi/apue # ./job_control &
[1] 7019
jiayi:/home/jiayi/apue # ps -o pid,ppid,pgrp,sid,tpgid,command
  PID  PPID  PGRP   SID TPGID COMMAND
 6631  4167  6631  6631  7029 bash
 7019  6631  7019  6631  7029 ./job_control
 7020  7019  7019  6631  7029 ./job_control
 7029  6631  7029  6631  7029 ps -o pid,ppid,pgrp,sid,tpgid,command
jiayi:/home/jiayi/apue # ./job_control_1 7019
^C^C^C^C^C^C^^^^^^

呵呵, Control-C  Control- 发送的信号都没被响应,只在是打印转义字符… 除了看着终端,束手无策。。。

另开一个并键入 ps 命令

jiayi:/home/jiayi/apue # ps -e -o pid,ppid,pgrp,sid,tpgid,command | tail -6
 6918  2836  2836  2836    -1 pickup -l -t fifo -u
 7019  6631  7019  6631  7031 ./job_control
 7020  7019  7019  6631  7031 ./job_control
 7031  6631  7019  6631  7031 ./job_control_1 7019
 7037  6576  7037  6576  7037 ps -e -o pid,ppid,pgrp,sid,tpgid,command
 7038  6576  7037  6576  7037 tail -6

TPGID 一栏都是 7031 ,说明上一个终端的 前台进程组ID7031 ,但是我们并没有找到这个 进程组./job_control _1 7161 在执行setpgid(0,new_gpig) 函数之前曾是 7031 进程组的 Leader ,但随后将自己放进了 7019 进程组,7031 进程组变空,随后消失。但终端还认为前台进程组ID 仍是 7031 ,所以 SIGINTSIGQUIT 都被送递到一个不存在的 进程组,看起来好像是被阻塞了。。。

setpgid() 还真是不好对付,以后用起时一定小心。

signal sigaction

Posted by jiayi | Posted in APUE | Posted on 02-11-2008

4

As people say, the old signal() had number of problems:1. The disposition for a signal was automatically reset to its defualt each time the signal occured. So we had to reestablish the handler on catching the signal. 2.There is, however, another subtle problem after reestablish the handler: There is a window of time –after the signal has occured,but before the call to signal in the signal handler. If the same signal occured int the window of time, it would cause the default action to occur and the handler would never fetch it. 3.It couldn’t control the blocking stat of signal. And so on

Fortunately, the new signal() implemented by sigaction() has fixed those problems. So the following will take us look into the new signal() (My environment is Linux 2.6.25)

C code


#include<stdio.h>

#include<stdlib.h>
#include<signal.h>
#include<errno.h>

sigset_t newmask,oldmask,pendmask;

void sig_int(int signo);
int main()
{
    if(signal(SIGINT,sig_int)==SIG_ERR) {
        perror("SIGNAL");
        exit(1);
    }

    sigemptyset(&newmask);
    sigaddset(&newmask,SIGCONT);
    if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0) {
        perror("SIGPROCMASK");
        exit(2);
    }
   
    sleep(10);
    printf("\nNo SIGINT be blocked and return to main(). \n");     /* 中断系统调用后,没有自动restart it */

    /* Reset signal mask which unblocks SIGINT */
    if (sigprocmask(SIG_SETMASK,&oldmask,NULL)<0) {
        perror("SIGPROCMASK");
        exit(3);
    } else {
        if(sigismember(&oldmask,SIGCONT))
            printf("Out of the handler, SIGCONT blocked——————————-\n");
        else
            printf("Out of the handler, SIGCONT unblocked———————————-\n");

        if(sigismember(&oldmask,SIGINT))
            printf("Out of the handler, SIGINT blocked———————————-\n");
        else
            printf("Out of the handler, SIGINT unblocked———————————-\n");
    }

    printf("Sleeping in main()…\n");
    sleep(10);
    exit(0);
}

void sig_int(int signo)
{
    printf("nncaught SIGINT\n");

    if(sigprocmask(SIG_BLOCK,NULL,&newmask)<0) {
        perror("SIGPENDING");
        exit(3);
    }

    if(sigismember(&newmask,SIGINT))
        printf("SIGINT blocked\n");
    else
        printf("SIGINT unblocked\n");

    if(sigismember(&newmask,SIGCONT))
        printf("SIGCONT blocked\n");
    else
        printf("SIGCONT unblocked\n");

    printf("Sleeping…\n");
    sleep(5);
}

jiayi:/home/jiayi/apue # ./queue0
^C                    #SIGINT wasn’t be blocked. It was caught immediately on its occurence

caught SIGINT     # In the signal handler
SIGINT blocked   # SIGINT was automatically blocked by signal() in its own handler,
SIGCONT blocked   # SIGCONT was blocked out of the signal handler manually
Sleeping…            # signal handler sleeping
^C^C^C^C^C        # SIGINT was blocked…

caught SIGINT     # When the last signal handler return, the delivery mask was reset to its previous value. So the blocked SIGINT was delivered to the signal handler again. We send SIGINT 5 times, but signal handler merely caught it once. Because SIGINT isn’t a reliable signal and it can’t be queued.
SIGINT blocked   # Be blocked again…
SIGCONT blocked   # Still be blocked…
Sleeping…            # Sleep in the signal handler

No SIGINT be blocked and return to main().
Out of the handler, SIGCONT unblocked———————————-  # As we set the delivery mask to its old value, SIGCONT wasn’t blocked
Out of the handler, SIGINT unblocked———————————-
Sleeping in main()
^C

caught SIGINT   # Caught SIGINT immediately
SIGINT blocked # Automatically be blocked
SIGCONT unblocked
Sleeping…
^C^C^C^C^C

caught SIGINT
SIGINT blocked
SIGCONT unblocked
Sleeping…

The output above tells us:
1.We needn’t  reestablish the handler on catching the signal.There is no window of time. It’s reliable.
2.SIGXXX automatically be blocked in its own handler
3.The process would block until signal handler absolutely return(ie, there is no blocked signal in the handler)

Now, show a implemention of signal() with sigaction()

C code


typedef
void Sigfunc(int);

Sigfunc *signal(int signo, Sigfunc *func)
{
    struct sigaction    act, oact;

    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if (signo == SIGALRM) {
#ifdef  SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;
#endif
    } else {
#ifdef  SA_RESTART
        act.sa_flags |= SA_RESTART;
#endif
    }  
    if (sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return(oact.sa_handler);
}

If we don’t want the signal which triggered the handler to be automatically blocked, the SA_NODEFER flag should be set as the following code added
        act.sa_flags |= SA_NODEFER

Unix time functions

Posted by jiayi | Posted in APUE | Posted on 18-10-2008

3



Ownership of New Files and Directories

Posted by jiayi | Posted in APUE | Posted on 14-10-2008

4

When we described the creation of a new file using either open or creat, we never said what values were assigned to the user ID and group ID of the new file. We’ll see how to create a new directory when we describe the mkdir function. The rules for the ownership of a new directory are identical to the rules in this section for the ownership of a new file.

The user ID of a new file is set to the effective user ID of the process. POSIX.1 allows an implementation to choose one of the following options to determine the group ID of a new file.

  1. The group ID of a new file can be the effective group ID of the process. # Linux 2.6.22.5-31 compliant this rule

  2. The group ID of a new file can be the group ID of the directory in which the file is being created.

    FreeBSD 5.2.1 and Mac OS X 10.3 always uses the group ID of the directory as the group ID of the new file.

    The Linux ext2 and ext3 file systems allow the choice between these two POSIX.1 options to be made on a file system basis, using a special flag to the mount(1) command. On Linux 2.4.22 (with the proper mount option) and Solaris 9, the group ID of a new file depends on whether the set-group-ID bit is set for the directory in which the file is being created. If this bit is set for the directory, the group ID of the new file is set to the group ID of the directory; otherwise, the group ID of the new file is set to the effective group ID of the process.

Using the second optioninheriting the group ID of the directoryassures us that all files and directories created in that directory will have the group ID belonging to the directory. This group ownership of files and directories will then propagate down the hierarchy from that point. This is used, for example, in the /var/spool/mail directory on Linux.

Directories are created with the mkdir function and deleted with the rmdir function.

 

#include <sys/stat.h>

int mkdir(const char *pathname, mode_t mode);

    Returns: 0 if OK, 1 on error

An empty directory is deleted with the rmdir function. Recall that an empty directory is one that contains entries only for dot and dot-dot. 

 

#include <unistd.h>

int rmdir(const char *pathname);

Returns: 0 if OK, 1 on error