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

Linux 僵死进程(zombie)

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

3

Linux 中有一种“死不瞑目”的进程,叫僵死进程(zombie)。一句话定义僵死进程:In UNIX System terminology, a process that has terminated, but whose parent has not yet waited for it, is called a zombie. 这些进程可以通过 ps 命令查看:ps -el ,带有 Z 标记的为僵死进程。也可以 ps -ef | grep defunc

一个进程exit()后,内核会将它的exit status 转换为 termination status,并由内核记录。可见这个结束的进程并没有真正的被销毁,虽然它已经不占用任何内存和cpu资源,没有任何可执行代码,也不能被调度,但却残留在进程列表里,一旦这种进程过多,系统性能肯定受其影响。这就需要它的父进程为它“收尸”,来获取已经terminate的子进程的终止状态(通常wait()waitpid() 来完成)。如果父进程在子进程之前退出,子进程会变成僵死进程了么…?不会的,这些子进程会被1号进程init 收留,并由1号进程init为它们”收尸“。

为了更好的说明僵死进程这个东东,我们人为的制造一个。。。 

C code


#include<stdio.h>

#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<sys/stat.h>
#include <sys/wait.h>

int
main(void)
{
    pid_t pid;
    pid_t pid1;
    pid_t wait_pid;

    printf("The parent’s pid is %dn",getpid());
    if ((pid = fork()) < 0)  
    {  
        perror("fork error");
    }  
    else if (pid == 0)  
    {       /* first child */
        if ((pid = fork()) < 0)
            perror("fork error");
        else if (pid > 0)
        {
            printf("First child of the parent is %dn",getpid());
            exit(0);
        }

        sleep(1);
        printf("First child’s child awaked…n");
        printf("pid = %d, parent pid = %dn",getpid(), getppid());
        exit(0);
    }
    else /*The parent process*/
    {
        if((pid1=fork())<0)
            perror("Fork");
        else if(pid1==0)
        {
            printf("Second child of the parent is %dn",getpid());
            exit(0);
        }
    }

    sleep(3);
    printf("parent awaked…n");
    if ((wait_pid=waitpid(pid1, NULL, 0)) == pid1)  // wait for second child
    {
        printf("seconde child has been waited: value of wait_pid is %dn",wait_pid);
    }
    while(1)
        ;

    exit(0);
}

编译运行得到

jiayi:/home/jiayi/apue # ./waitpid
The parent’s pid is 20334
Second child of the parent is 20337
First child of the parent is 20335
First child’s child awaked…
pid = 20336, parent pid = 1
parent awaked…
seconde child has been waited: value of wait_pid is 20337

另一个终端运行 ps -ef 得到最后三行

root     20334  9667 90 21:55 pts/7    00:37:28 ./waitpid
root     20335 20334  0 21:55 pts/7    00:00:00 [waitpid] <defunct>
jiayi  20459 20152  0 22:37 pts/2    00:00:00 ps -ef

一句一句分析程序打印的结果
此程序的进程号为20334;父进程第二个子进程的进程号为20337;父进程第一个子进程的进程号为20335(从这两个进程的打印顺序可以看出,两个进程存在竞争);20335的子进程 20336在睡觉,醒来后发现它的父进程变成了1,这是因为2033520336结束之前已经结束,20336进程被1init收留;父进程在睡觉,醒来后通过waitpid()成功获取第二个子进程20337的终止信息,然后很没素质的作起了无限循环…

至此,父进程在循环,两个子进程以及第一个子进程的子进程都已终止。其中第二个子进程 20337 被收尸,彻底销毁;子进程的子进程被1号init 收尸,也完全销毁;第一个子进程呢…? 看一下 ps 命令的结果,同样一句一句看:
20334号父进程在运行,因为它在作无限循环…;20335号子进程由defunct 标记,说明是一个僵死进程…… 另外两个子进程没有被发现,因为它们已经彻底解脱…

实验还没有结束,这时我们摁下 ctrl + c 将无限循环的父进程 20334 干掉,重新 ps -ef ,那个僵死进程也不见了。。。这是因为父进程 20334 被干掉后,僵死进程被1init ”收尸“,从而彻底解脱。。。

额,弄一个僵死进程出来还挺不容易的。。。

Unix time functions

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

3



Ext3 file system

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

4

File system

Super
Block
Group
Descriptors
Block
Group 1
Block
Group 2

Block
Group N

Block
Bitmap
Inode
Bitmap
Inode
Table
Data
Blocks

From the figuires above we get:

Every block group is associated with a group descriptor. All of the group descriptors are congregated following the Super Block tightly.

In the group descriptor,there is a pointer points to the refered block group’s block bitmap.Every bit in the block bitmap indicates a block.0 represents the block has been written;1 represents the block is free.The size of block bitmap is just the same as a block.So if  we assume a block is S bytes.The block bitmap can record S*8 blocks.We get that a block group is 8*S*S bytes at most.

There is another pointer in the group descriptor pointing to the inode bitmap.This inode bitmap owns the same size of a block too.Every bit in it refers to a i-node.The i-node laying on the hard disk refers to the file on the file system.The file types are as the follow:

File type

   

regular file

 

 

directory

   

symbolic link

   

character special

   

block special

   

socket

   

FIFO

   

Another important pointer in descriptor points to the I-node table.The I-node table is larger than a block.It gathers all the i-nodes in the block group.

The structure of Supper Block

struct ext3_super_block {
/*00*/ __u32 s_inodes_count;      /* inodes count */
       __u32 s_blocks_count;      /* blocks count */
       __u32 s_r_blocks_count;    /* reserve blocks count */
       __u32 s_free_blocks_count; /* free blocks count */
/*10*/ __u32 s_free_inodes_count; /* free inodes count */
       __u32 s_first_data_block;  /* firest data block */
       __u32 s_log_block_size;    /* size of a block */
       __s32 s_log_frag_size;     /* Ignore */
/*20*/ __u32 s_blocks_per_group;  /* number of blocks per block group */
       __u32 s_frags_per_group;   /* Ignore */
       __u32 s_inodes_per_group;  /* number of inodes per block group */
       __u32 s_mtime;             /* Mount time */
/*30*/ __u32 s_wtime;             /* Write time */
       __u16 s_mnt_count;         /* Mount count */
       __s16 s_max_mnt_count;     /* Maximal mount count */
       __u16 s_magic;             /* Magic label */
       __u16 s_state;             /* File system state */
       __u16 s_errors;            /* Behaviour when detecting errors */
       __u16 s_minor_rev_level;   /* minor revision level */
/*40*/ __u32 s_lastcheck;         /* time of last check */
       __u32 s_checkinterval;     /* max. time between checks */
       __u32 s_creator_os;        /* Ignore */
       __u32 s_rev_level;         /* Revision level */
/*50*/ __u16 s_def_resuid;        /* Default uid for reserved blocks */
       __u16 s_def_resgid;        /* Default gid for reserved blocks */
       __u32 s_first_ino;         /* First non-reserved inode */
       __u16 s_inode_size;        /* size of inode structure */
       __u16 s_block_group_nr;    /* block group # of this superblock */
       __u32 s_feature_compat;    /* compatible feature set */
/*60*/ __u32 s_feature_incompat;  /* incompatible feature set */
       __u32 s_feature_ro_compat; /* readonly-compatible feature set */
/*68*/ __u8  s_uuid[16];          /* 128-bit uuid for volume */
/*78*/ char  s_volume_name[16];   /* volume name */
/*88*/ char  s_last_mounted[64];  /* directory where last mounted */
/*C8*/ __u32 s_algorithm_usage_bitmap; /* Ignore */
       __u8  s_prealloc_blocks;        /* Ignore */
       __u8  s_prealloc_dir_blocks;    /* Ignore */
       __u16 s_padding1;               /* Ignore */
/*D0*/ __u8  s_journal_uuid[16]; /* uuid of journal superblock */
/*E0*/ __u32 s_journal_inum;     /* log file's inode number */
       __u32 s_journal_dev;      /* log file's device number */
       __u32 s_last_orphan;      /* start of list of inodes to delete */
/*EC*/ __u32 s_reserved[197];    /* Ignore */
};

The structure of group descriptor

struct ext3_group_desc
{
 __u32 bg_block_bitmap;      /* block pointer points to block bitmap */
 __u32 bg_inode_bitmap;      /* block pointer points to inode bitmap */
 __u32 bg_inode_table;       /* block pointer points to inodes table */
 __u16 bg_free_blocks_count; /* free blocks count */
 __u16 bg_free_inodes_count; /* free inodes count */
 __u16 bg_used_dirs_count;   /* number of direcotries */
 __u16 bg_pad;               /* Ignore */
 __u32 bg_reserved[3];       /* Ignore */
};

The structure of I-node

struct ext3_inode {
 __u16 i_mode;    /* File mode */
 __u16 i_uid;     /* Low 16 bits of Owner Uid */
 __u32 i_size;    /* size of file,byte unit */
 __u32 i_atime;   /* Access time */
 __u32 i_ctime;   /* Creation time */
 __u32 i_mtime;   /* Modification time */
 __u32 i_dtime;   /* Deletion Time */
 __u16 i_gid;     /* Low 16 bits of Group Id */
 __u16 i_links_count;          /* Links count */
 __u32 i_blocks;               /* blocks count */
 __u32 i_flags;                /* File flags */
 __u32 l_i_reserved1;          /* Ignore */
 __u32 i_block[EXT3_N_BLOCKS]; /* array of block pointers */
 __u32 i_generation;           /* Ignore */
 __u32 i_file_acl;             /* Ignore */
 __u32 i_dir_acl;              /* Ignore */
 __u32 i_faddr;                /* Ignore */
 __u8  l_i_frag;               /* Ignore */
 __u8  l_i_fsize;              /* Ignore */
 __u16 i_pad1;                 /* Ignore */
 __u16 l_i_uid_high;           /* Ignore */
 __u16 l_i_gid_high;           /* Ignore */
 __u32 l_i_reserved2;          /* Ignore */
};


I-node points to directory blocks

From the figuire above we get:

The structure of directory are allocated out of the i-node array (i-node table). It is allocated in the data blocks with the ordinary data together.
Ordinary file i-node points to the ordinary data block. Direcotery i-node points to the direcotery blocks.

Directory block points to the associated i-node


From the figuire above we get:

Directories are structured in a hierarchical tree. Each directory can contain files and subdirectories.
Directories are implemented as a special type of files. Actually, a directory is a file containing a list of entries. Each entry contains an inode number and a file name. When a process uses a pathname, the kernel code searchs in the directories to find the corresponding inode number. After the name has been converted to an inode number, the inode is loaded into memory and is used by subsequent requests.

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