星海's Blog

老头初学编程
APUE中,error.c几个错误处理函数的不同
APUE学习笔记(二)

APUE学习笔记(一)

星海 posted @ 2011年7月15日 22:39 in APUE && UNP , 4680 阅读
/*  第一章: UNIX基础知识

内核的接口被称为系统调用(system call)
shell是一种特殊的应用程序,它为运行其他应用程序提供了一个接口

*/

#include <stdio.h>
#include	<dirent.h>
#include <stdlib.h>


int main(int argc, char *argv[])
{
        DIR *dp;
        struct dirent *dirp;

        if (argc != 2) {
                printf("fuck damn\n");
                exit(1);
        }

        if ((dp = opendir(argv[1])) == NULL) {
                perror("fuck you");
        }

        while ((dirp = readdir(dp)) != NULL) {
                printf("%s\n", dirp->d_name);
        }

        closedir(dp);
        return 0;
}

// for和跟随其后的exec两者的组合是某些操作系统所称的产生(spawn)一个新进程。
/* 1.6 程序和进程
 */

#include	<stdio.h>
#include	<unistd.h>
#include	<string.h>
#include	<sys/wait.h>
#include	<stdlib.h>

int main(void)
{
        int status;
        char buf[30];
        pid_t pid;

        while (fgets(buf, 29, stdin) != NULL) {
                if (buf[strlen(buf)-1 ] == '\n') {
                        buf[strlen(buf) -1 ] = '\0';
                }

                printf("virtual shell: ");
                if ((pid = fork()) < 0) {
                        perror("fuck\n");
                        exit(126);
                } else if (pid == 0) {
                        execlp(buf, buf, (char *)0);

                        /* 子进程继承父进程的副本,获得父进程的数据空间、堆和栈的副本。exec只是用一个全新的程序替换了当前进程的正文、数据、堆和栈段。所以如果execlp成功,perror这行不会执行,但如果失败,此时仍是在副本之中,执行perror。 */
                        perror("couldn't exec ");
                        exit(127);              /* 退出的是子进程,不会影响整个程序 */
                }

                if ((pid = waitpid(pid, &status, 0)) < 0) {
                        perror("waitpid error\n");
                }
        }
        return 0;
}

/* 在一个进程内的所有线程共享同一地址空间、文件描述符、栈以及相关的属性。需要采取同步措施以避免不一致性。
 */

/* 1.10 时间值
 */
//
// 日历时间:
//	1970/1/1以来国际标准时间(UTC)所经过的秒数累计。 time_t
//
// 进程时间:
//	也被称为CPU时间,以时钟滴答计算。 clock_t
//	用户CPU时间,是执行用户指令所使用的时间。
//	系统CPU时间,是为该进程执行内核程序所经历的时间。如:进程调用read/write,在内核执行read/write所花费的时间。
//	用户CPU时间 + 系统CPU时间 = CPU时间
//
// 实际时间:
//	realtime ?
//

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  2.5 限制
 *  Description:
 * =====================================================================================
 */

/*
 * UNIX系统提供了以下三种限制
 * (1) 编译时限制    ISO C定义的限制都是编译时限制
 * (2) 不与文件或目录相关联的运行时限制(sysconf函数)
 * (3)
 * 与文件或目录相关联的限制(pathconf和fpatchconf),pathconf的返回值是基于工作目录的相对路径名的最大长度。所以,如果要确定“一些值”的话,请将工作目录改为"/"。
 */

/*
 * UNIX系统同样提供了以下三种是否有相关选项的处理方法
 * (1) 编译时选项定义在 <unistd.h>中
 * (2) 与文件或目录无关联的选项确定(sysconf函数)
 * (3) 与文件或目录有关联的选项确定( pathconf或fpathconf)
 * 与文件或目录相关联的限制(pathconf和fpatchconf)
 */

/* 如果编译一个程序时,希望它只使用POSIX (XOPEN)的定义而不使用任何实现定义的限制,可定义 #define _POSIX_C_SOURCE 200809L 或 #define _XOPEN_SOURCE 700
 * #ifdef __STDC__ .... #else 功能测试宏用于测试C编译器是否符号ISO C标准
 */


/* 第3章 文件I/O
 */
/*
 * creat的不足之处在于它以只写方式打开所创建的文件。若要先写再读该文件,必须先creat, close再调用open。另外creat函数在<fcntl.h>头文件中定义
 * 检测lseek返回时是否出错,应当检测其是否为 -1,因为有的系统(freebsd)lseek返回值off_t可能是负值
 * 任何一个需要调用多个函数的操作都不可能是原子操作,因为在两个调用函数之间,内核有可能会临时挂起该进程。
 */
/*
 * 原子操作:
 * SUS包括了XSI扩展,该扩展允许原子性地定位搜索和执行IO。
 */

ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int filedes, void *buf, size_t nbytes, off_t offset);

int dup(filedes);                               /* 总是返回当前可用文件描述符中的最小值 */
int dup2(int filedes, int filedes2);
/*
 * 两个文件描述符指向同一个文件表项,共享同一文件状态标志以及文件偏移量。
 * 但每个文件描述符都有一套文件描述符标志。新描述符的执行时关闭(close - on - exec)标志总是由dup函数清除。
 * 文件描述符标志与文件状态标志有作用域的区别。前者只用于一个进程的一个文件描述符,而后者则适用于指向该给定文件表项的任何进程中的所有描述符。
 */

int fsync(int filedes);                         /* 等待写磁盘操作结束,然后返回,确保修改过得块立即写到磁盘上 */
int fdatasync(int filedes);                     /* 只影响文件数据部份,但fsync同步更新文件属性 */
void sync(void);                                /* 只是将所有修改过的块缓冲区排入写队列,然后就返回,不等待实际写磁盘操作结束 */
/*
 * fcntl函数有5种功能
 *  1,复制一个现有文件描述符 	F_DUPFD
 *  2,获得/设置文件描述符标记 	F_GETFD F_SETFD
 *  3,获得/设置文件状态标志 	F_GETFL F_SETFL
 *  4,获得/设置异步I/O所有权 	F_GETOWN F_SETOWN     接收SIGIO和SIGURG信号的进程ID(正数)或进程组ID(负数), F_GETOWN返回值为一个正的进程ID或负的进程组ID
 *  5,获得/设置记录锁 		F_GETLK F_SETLK F_SETLKW
 */

// 文件状态标志里的O_RDONLY O_WRONLY O_RDWR互斥,可用O_ACCMODE获取当前读写状态

第4章: 文件和目录

#include	<stat.h>

int stat(char *restrict pathname, struct stat *restrict buf);
int fstat(int filedes, struct stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);

/* lstat类似于stat,但是当命名的文件是一个符号链接时,lastat返回符号链接本身,
 * stat返回该符号链接所引用的文件信息。
 */

//文件类型宏,参数均为struct stat结构中的st_mode成员
//也可用屏蔽字S_IFMT与st_stat进行逻辑与运算
//
S_ISREG()                                       /* 普通文件 */
S_ISDIR()                                       /* 目录文件 */
S_ISCHR()                                       /* 字符特殊文件 */
S_ISBLK()                                       /* 块特殊文件 */
S_ISFIFO()                                      /* 管道或FIFO */
S_ISLNK()                                       /* 符号链接*/
S_ISSOCK()                                      /* 套接字 */
/*
 * 实际用户ID和实际组ID标识我们究竟是谁。
 * 有效用户ID,有效组ID以及附加组ID决定了我们文件的访问权限。
 * 保存的设置用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本。
 *
 * 通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID。
 * 每个文件都有一个所有者st_uid,有一个组所有者st_gid。可以在文件模式字
 * (st_mode)中设置一个标志,含义是“当执行此文件时,将进程的有效用户ID设置为文
 * 件的所有者ID st_uid,称为设置用户ID(set - user - ID)。st_gid类同。这两位可
 * 用常量S_ISUID和S_ISGID测试。
 *
 * 对目录的读权限,可获得在该目录中所有文件名的列表。
 * 如果要在open()中指定O_TRUNC标志,必须对文件有写权限。
 * 为了在一个目录中创建一个新文件或删除一个现有文件,必须对该目录具有写和执行
 * 权限。删除的话,对文件本身不需要有读写权限。
 *
 * 创建新文件时,新文件的用户ID等设置为进程的有效用户ID。
 * 关于组ID,POSIX.1允许下面两种作为新文件的组ID。
 * 1,新文件的组ID可以是进程的有效组ID
 * 2,新文件的组ID可以是它所在目录的组ID,FreeBSD和MAC总是如此。
 * Linux和Solaris下,如果目录的设置组id已经设置,则将新文件的组ID设置为目录的组ID,否则将其设为进程的有效组ID。
 *
 * access函数是按实际用户ID和实际组ID进行访问权限测试的。
 * sys/stat.h 为了改变一个文件的权限位(chmod fchmod),进程的有效用户ID必须等于文件的所有者ID,或者该进程必须具有超级用户权限。
 *
 */
S_ISUID 	执行时设置用户ID
S_ISGID 	执行时设置组ID
S_ISVTX 	保存正文(粘住位)
chmod fchmod无视umask值。
ls - l中,小写s,代表执行和设置用户(组)权限。大写S,代表非执行和设置用户(组)权限。

int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int filedes, uid_t owner, gid_t group);
int lchown(const char* pathname, uid_t owner, gid_t group); /* 更改符号链接本身 */
/*
 * 若_POSIX_CHOWN_RESTRICTED常量对指定的文件起作用,则:
 * 1,只有超级用户进程能更改该文件的用户ID。
 * 2,如果进程拥有此文件(其有效用户ID等于该文件的用户ID)
 * 2,参数owner等于 -1或文件的用户ID,并且参数group等于进程的有效组ID或附加组ID。
 * 这意味着,当_POSIX_CHOWN_RESTRICTED起作用时,你不能更改其他用户文件的用户ID。
 * 如果这些函数由非超级用户进程调用,则在成功返回时,该文件的设置用户ID和设置组ID位都会被清除。
 *
 * 在stat结构中,链接计数保存在st_nlink成员中,其基本系统数据类型是nlink_t。这种链接类型是硬件链接。
 *
 */

/* <stdio.h>中的remove函数,对于文件,与unlink相同,对于目录与rmdir功能相同。
 */

ssize_t readlink(const char *restrict pathname, char *restrict buf, size_t bufsize); /* 在buf中返回的符号链接内容不以null字符终止 */

#include 	<utime.h>
int utime(const char *pathname, const struct utimbuf *times);

/* 更改文件的访问和修改时间,
 * 如果times是一个空指针,则访问时间和修改时间两者都设置为当前时间。
 * 执行此操作,需要进程对该文件具有写权限或者进程的有效用户ID等于文件的所有者ID。
 *
 * 如果times是非空指针,需
 * 进程的有效用户ID等于文件的所有者ID,或者进程必须是一个超级用户进程,单单对文件只具有写权限是不够的。
 */
/*
 * 只有内核才能写目录,
 * 每个文件系统所在的存储设备都由其主次设备号表示。主设备号标识设备驱动程序,
 * 有时编码为与其通信的外设板;次设备号标识特定的子设备。在同一磁盘驱动器上的
 * 各文件系统通常具有相同的主设备号,而其次设备号却不同。只有字符特殊文件和块
 * 特殊文件才有st_rdev值。此值包含实际设备的设备号。
 * major minor两个宏来访问主、次设备号。
 */

 
#include	<stdio.h>
#include	<wchar.h>

int fwide(FILE *fp, int mode);
/* 若mode参数值为负,fwide将试图使指定的流是字节定向的。
 * 若mode参数值为正,fwide将试图使指定的流是宽定向的。
 * 若mode参数值为0,fwide将不试图设置流的定向,但返回标识该流定向的值。
 * 如果是宽定向,返回正值,若流是字节定向返回负值,未定向返回0值。
 * fwide无出错返回。可在fwide前清除errno,从fwide返回时再检查errno值。
 */

/* 术语冲洗(flush)说明标准IO缓冲区的写操作。在标准I / O库方面,flush意味着缓冲区中的内容写到磁盘上。在终端驱动程序方面,flush表示丢弃已存储在缓冲区中的数据。
 */

/* setbuf, setvbuf这些函数一定要在流已被打开后调用。 */

setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
/* _IOFBF _IOLBF _IONBF。 char *buf最好设为NULL,由标准I / O库自动为该流分配适当长度的缓冲区。*/

FILE *fopen(const char *restrict pathname, const char *restrict type);
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp); /* 在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。若该流已经定向,则freopen清除该定向。一般用于标准输入输出等重定向到文件 */
FILE *fdopen(int filedes, const char *type);    /* 因为是文件描述符,此函数常用于由创建管道和网络通信通道函数返回的描述符,因为这些特殊类型的文件不能用标准I/O fopen函数打开。因为描述符已经打开,所以fdopen不能截短文件。 */

/* 当以读和写类型打开一文件时(type中有"+"号),
 * 如果中间没有fflush、fseek、fsetpos或rewind,则在输出的后面不能直接跟随输入。
 * 如果中间没有fseek、fsetpos或rewind,或者一个输入操作没有到达尾端,则在输入操作后不能直接跟随输出。
 */

/* "r+"文件必须已经存在,并且不擦除文件以前的内容,默认直接从文件开头开始覆写。
 */

int getc(FILE *fp);
int fgetc(FILE *fp);

/* 两者的区别是,getc可被实现为宏,而fgetc不能实现为宏。这意味着,getc的参数不应当是具有副作用的表达式。可以得到fgetc的函数指针。
 * 如果到达文件结尾或出错则返回EOF,为区分这种不同,必须调用ferror或feof。
 */
int ferror(FILE *fp);
int feof(FILE *fp);

void clearerr(FILE *fp);                       /* 清除FILE对象中的出错标志和文件结束标志 */

int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);
/* 注意是指针。。。fgetpos将文件位置指示器的当前值存入pos指向的对象中。在以后调用fsetpos时,可用此值重新定位至该位置。
 */

char *tmpnam(char *ptr);                        /* 每次都产生一个不同路径名,最多调用次数是TMP_MAX,如若ptr不是NULL,则认为它指向长度至少是L_tmpnam个字符的数组。如果ptr是NULL,则每次调用,都会重写存放路径名的静态区,所以我们应当保存该路径名的副本。 */
FILE *tmpfile(void);                            /* 创建一个临时二进制文件,"wb+",在关闭该文件或程序结束时,将自动删除这种文件。 */

SUS XSI扩展部份
char *tempnam(const char *directory, const char *prefix); /* 如果prefix非NULL,它应当是最多包含5个字符的字符串,用其作为文件名的头几个字符。 */
int mkstemp(char *template);                    /* 该函数返回临时文件的打开文件描述符,可读写。mkstemp创建的临时文件不会自动被删除。
template是一个路径名,例如 /tmp/XXXXXX。如mkstemp成功返回会自动修改template字符串以反映临时文件的名字。 */

/* 第6章:系统数据文件和信息
 */

//vipw命令允许管理员使用该命令编辑口令文件。

struct passwd {
        char *pw_name;          /* Username.  */
        char *pw_passwd;        /* Password.  */
        __uid_t pw_uid;         /* User ID.  */
        __gid_t pw_gid;         /* Group ID.  */
        char *pw_gecos;         /* Real name.  */
        char *pw_dir;           /* Home directory.  */
        char *pw_shell;         /* Shell program.  */
};

#include	<pwd.h>
struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);

struct passwd *getpwent(void);  /* 返回口令文件的下一项 */
void setpwent(void);            /* 反绕文件,最好在程序开始处调用一次,保护性措施。*/
void endpwent(void);            /* 使用getpwent查看完口令文件后,一定要调用endpwent关闭这些文件。 */



#include	<shadow.h>
struct spwd {
        char *sp_namp;          /* Login name.  */
        char *sp_pwdp;          /* Encrypted password.  */
        long int sp_lstchg;     /* Date of last change.  */
        long int sp_min;        /* Minimum number of days between changes.  */
        long int sp_max;        /* Maximum number of days between changes.  */
        long int sp_warn;       /* Number of days to warn user to change
				   the password.  */
        long int sp_inact;	/* Number of days the account may be
				   inactive.  */
        long int sp_expire;	/* Number of days since 1970-01-01 until
				   account expires.  */
        unsigned long int sp_flag;              /* Reserved.  */
};

struct spdw *getspnam(const char *name);
struct spdw *getspent(void);

void setspent(void);
void endspent(void);


struct group {
        char *gr_name;          /* Group name.  */
        char *gr_passwd;        /* Password.    */
        __gid_t gr_gid;         /* Group ID.    */
        char **gr_mem;          /* Member list. */
};
#include	<grp.h>

struct group *getgrgid(gid_t gid);
struct group *getgrnam(const char *name);

struct group *getgrent(void);
void setgrent(void);
void endgrent(void);

/* 为了获取和设置附加组ID,提供了以下几个函数
 */

#include	<unistd.h>
#include	<grp.h>
// getgroups将各附加组ID填写到数组grouplist中,元素最多为gidsetsize个。实际填写到数组中的附加组id数目由函数返回,如果出错返回-1。
// 作为一个特例,若gidsetseize为0,函数只返回附加组id数目,对数组grouplist不作修改。这样可以确定数组grouplist的长度。

int getgroups(int gidsetsize, gid_t grouplist[]);

int setgroups(int ngroups, const gid_t grouplist[]); /* 特权操作,ngroups的值不能大于NGROUP_MAX */
int initgroups(const char *username, gid_t basegid); /* 特权操作,login用到,这两个函数还需要进一步了解。 */

// freebsd中,阴影文件是/etc/master.passwd。

utmp文件记录当前登录进系统的各个用户;wtmp文件,跟踪各个登录和注销事件。last命令读wtmp文件并打印记录。


#include	<time.h>
time_t time(time_t *calptr);
int gettimeofday(struct timeval *restrict tp, void *restrict tzp); /* 总是返回0值,SUS中,tzp唯一合法值为NULL。 */

struct timeval {
        time_t tv_sec;
        long tv_usec;           /* 微秒 */
}

struct tm {
        int tm_sec;			/* Seconds.	[0-60] (1 leap second) */
        int tm_min;			/* Minutes.	[0-59] */
        int tm_hour;			/* Hours.	[0-23] */
        int tm_mday;			/* Day.		[1-31] */
        int tm_mon;			/* Month.	[0-11] */
        int tm_year;			/* Year	- 1900.  */
        int tm_wday;			/* Day of week.	[0-6] */
        int tm_yday;			/* Days in year.[0-365]	*/
        int tm_isdst;			/* DST.		[-1/0/1]*/

#ifdef	__USE_BSD
        long int tm_gmtoff;		/* Seconds east of UTC.  */
        __const char *tm_zone;	/* Timezone abbreviation.  */
#else
        long int __tm_gmtoff;		/* Seconds east of UTC.  */
        __const char *__tm_zone;	/* Timezone abbreviation.  */
#endif
};

struct tm *gmtime(const time_t *calptr); /* TZ,localtime */
struct tm *localtime(const time_t *calptr); /* UTC time */

time_t mktime(struct tm *tmptr); /* TZ,local time */

char *asctime(const struct tm *tmptr);
char *ctime(const time_t *calptr);              /* TZ,localtime */

size_t strftime(char *restrict buf, size_t maxsize,
                const char *restrict format,
                const struct tm *restrict tmptr); /* TZ,localtime */
/* 第7章:进程环境
 */

/* 有8种方式使进程终止(termination),其中5种为正常终止,他们是
 * 1,从main返回 return
 * 2,调用exit
 * 3, 调用_exit或_Exit  ISO C定义_Exit,POSIX.1说明_exit,UNIX系统中两者同义
 * 4, 最后一个线程从其启动例程返回  进程终止状态为0,与线程返回值无关。
 * 5,最后一个线程调用pthread_exit  这样进程终止状态总是0。
 * 异常终止有3种方式,他们是
 * 1,调用abort  SIGABRT信号,下一种异常终止的特例
 * 2,接到一个信号并终止
 * 3,最后一个线程对取消请求做出相应。
 *
 * 内核使程序执行的唯一方法是调用一个exec函数。
 * 进程自!!愿!!!终止的唯一方法是显式或隐式的(通过exit)调用_exit或_Exit。
 * 不管进程如何终止,最后都会执行内核中同一段代码。为相应进程关闭所有打开文件
关闭所有打开文件描述符,释放它所使用的存储器等。
*/

#include	<stdlib.h>
int atexit(void (*func)(void));
extern char **environ;

int putenv(char *str);
int setenv(const char *name, const char *value, int rewrite); /* 若rewrite非0,则首先删除其现有定义。若rewrite为0,则不删除其现有定义。 */
/* putenv和setenv差别:
 * Linux和Solaris中,putenv实现将传送给它的字符串地址作为参数直接放入环境表中。
 * 如果字符串存放在栈中,且作为参数,函数返回时,栈销毁,可能造成错误。
 */

#include	<setjmp.h>
int setjmp(jmp_buf env);                        /* 若直接调用则返回0,若从longjmp调用返回则返回longjmp参数里的val值 */
void longjmp(jmp_buf env, int val);             /* 声明为全局或静态变量的值在执行longjmp时保持不变。volatile也可。 */

/*
 * 全局、静态和易失变量不受编译器优化的影响。
 * getlimit、setrlimit具体参数请参考manpage。
 *
 * 更改资源限制时,遵循下列三条原则:
 * 1,任何一个进程都可将一个软限制值改为小于或等于其硬限制值。
 * 2,任何一个进程都可以降低其硬限制值,但它必须大于或等于其软限制值。对于普通用户而言这是不可逆的。
 * 3,只有超级用户进程可以提高其硬限制值。
 *
 */
/* 第8章:进程控制
 */

/* 父进程设置的文件锁不会被子进程继承
 * 子进程的未处理的闹钟(alarm)被清除
 * 子进程的未处理信号集设置为空集
 */

/*
 * vfork创建子进程的目的是exec一个新程序,并不将父进程的地址空间完全复制到子进程中。
 * 子进程调用exec或exit之前,它在父进程的地址空间中运行。
 * vfork区别于fork:vfork确保子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。
 * 如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
 */

/* _exit并不执行标准I/O缓冲的冲洗操作。
 *
 * vfork中,如果子进程调用exit,则该程序的输出是不确定的,它依赖于标准I/O库的实现。
 * 情况1:exit实现冲洗所有标准I/O流,如果这是函数唯一的动作,那么输出与
 * 子进程调用_exit完全相同。
 * 情况2:如果该实现也关闭标准I/O流,那么表示标准输出FILE对象的相关存储区会被清0。
 * 因为子进程借用了父进程的地址空间,所以父进程恢复运行并调用printf时,也就不会产生任何输出。
 *
 *
 *
 *
 * 大多数exit的现代实现不再关闭流,因为进程即将终止,那时内核将关闭在进程中哦
进程中已打开的所有文件描述符,在库中关闭他们。比如Linux

课后题8.1我不明白,为什么fclose(stdout)可行,而close(STDOUT_FILENO)不可行。
*/

/* 对于父进程已经终止的所有进程,他们的父进程都改变为init进程。我们称这些进程
 * 为孤儿进程,由init进程领养。
 * 一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,
 * 释放它仍占用的资源)的进程被称为僵死进程。
 */

#include	<sys/wait.h
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
/* 如果成功返回则返回进程ID,0。如果出错则返回-1。
 * pid = -1,等待任一子进程。
 * pid > 0,等待其进程ID与pid相等的子进程。
 * pid = 0,等待其组ID等于调用进程组ID的任一子进程。
 * pid = -1,等待其组ID等于pid绝对值的任一子进程。
 *
 * options常量:
 * 1,WCONTINUED 	若实现作业控制,如果PID指定的任意子进程在暂停后已经继
已经继续,但其状态尚未报告,则返回其状态。
 * 2,WNOHANG 		若由PID指定的子进程并不是立即可用的,则waitpid不阻塞,
 * 其返回值为0。
 * 3,WUNTRACED 	若某实现支持作业控制,而由pid指定的任一子进程已处于暂
 *暂停状态,并且自暂停状态以来还未报告过,则返回其状态。
 * * */

WIFEXITED(status)        /* 若为正常终止子进程返回的状态则为真,
			    可执行WEXITSTATUS(status)来获取子进程传给exit族参数的低8位 */
WIFSIGNALED(status)      /* 异常终止状态宏测试。WTERMSIG(status)  WCOREDUMP(status)  */
WIFSTOPPED(status)       /* 暂停子进程返回的状态,WSTOPSIG(status)*/
WIFCONTINUED(status)     /* 作业控制暂停后已经继续的子进程反回状态。 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>

pid_t wait3(int *status, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
/* 如果成功则返回进程ID,如果出错则返回-1。
 * rusage为资源统计信息,包括用户CPU时间总量、系统CPU时间总量、页面出错次数,
 * 接受到的信号次数等。有关细节参阅 getrusage(2)手册页。
 */

/* race condition(竞争条件),polling(轮询),
 */
#include <unistd.h>

extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
           ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char * const envp[]); /* 只有execve是内核系统调用,其它都是库函数 */
/* 若函数执行出错返回-1,否则不返回值。
 *
 * execl、execlp、execle着三个函数表示命令行参数的一般方法是:
 * char *arg0, char *arg1, ..., (char *) 0;
 * 如果用常数0表示一个空指针,必须将它强制转换为一个字符指针,否则将它解释为
 *整形参数。
 *
 * 字母p表示该函数取filename为参数,并且用PATH环境变量寻找可执行文件。
 * 字母l表示该函数取一个参数表。
 * 字母v表示该函数取一个argv[]矢量。
 * 字母e表示该函数取envp[]数组,而不是使用当前环境。
 */

/*
  *
  * 执行新程序的进程还保持了进程的下列特征:
  * 进程ID和父进程ID,实际用户ID和实际组ID,闹钟尚余留时间,当前工作目录,文件
  文件创建屏蔽字,文件锁,进程信号屏蔽,未处理信号,资源限制,tms_utime,tms_stime,tms_cutime,tms_cstime值等。
  * 除非对设置了 打开文件描述符的close-on-exec标志,否则系统默认在执行exec后仍保持这种描述符打开。
  * 有效ID是否改变则取决于所执行程序文件的设置用户ID位和设置组ID位是否设置。
  *
  * All process attributes are preserved during an execve(), except the following:

*      The dispositions of any signals that are being caught are reset to the default (signal(7)).

*      Any alternate signal stack is not preserved (sigaltstack(2)).

*      Memory mappings are not preserved (mmap(2)).

*      Attached System V shared memory segments are detached (shmat(2)).

*      POSIX shared memory regions are unmapped (shm_open(3)).

*      Open POSIX message queue descriptors are closed (mq_overview(7)).

*      Any open POSIX named semaphores are closed (sem_overview(7)).

*      POSIX timers are not preserved (timer_create(2)).

*      Any open directory streams are closed (opendir(3)).

*      Memory locks are not preserved (mlock(2), mlockall(2)).

*      Exit handlers are not preserved (atexit(3), on_exit(3)).

*      The floating-point environment is reset to the default (see fenv(3)).

The  process  attributes  in  the preceding list are all specified in POSIX.1-2001.  The following Linux-specific process attributes are
also not preserved during an execve():

*  The prctl(2) PR_SET_DUMPABLE flag is set, unless a set-user-ID or set-group ID program  is  being  executed,  in  which  case  it  is
   cleared.

*  The prctl(2) PR_SET_KEEPCAPS flag is cleared.

*  The process name, as set by prctl(2) PR_SET_NAME (and displayed by ps -o comm), is reset to the name of the new executable file.

*  The termination signal is reset to SIGCHLD (see clone(2)).
*/

#include	<unistd.h>
int setuid(uid_t uid);
int setgid(uid_t uid);
int seteuid(uid_t uid);
int setegid(uid_t uid);

/* 改变用户ID的规则:
 * 若用户具有超级用户特权,则setuid函数将实际用户ID,有效用户ID以及保存的设置用户ID均设置为uid。
 * 若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置用户id,则setuid将有效用户ID设置为uid,不改变实际用户ID和保存的设置用户ID。
 * 如果以上两个条件都不满足,则将errno设置为EPERM,并返回-1。
 */

/* 关于内核维护的三个用户ID,还要注意以下几点
 * 只有超级用户进程可以改变实际ID。
 * 仅当对程序文件设置了设置用户ID位时,exec函数才会设置有效用户ID。结合以上,
 * 任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或设置用户ID。不能将
 * 有效用户ID设置为任意随机值。*/


int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);
/* 如果其中任一参数的值为-1,则表示相应的ID应当保持不变。
 * 一个非特权用户总能交换实际用户ID和有效用户ID。
 */

/* 当被执行文件是解释器文件时,argv[0]是该解释器的pathname,argv[1]是解释器中
 * 的可选参数,其余参数是exec后的pathname,参数等*/

system()在实现中调用了fork, exec, waitpid,因此有三种返回值。
/* 1,如果fork失败或者waitpid返回除EINTR之外的出错,则返回 -1,而且errno中设置错误类型值。
 * 2,如果exec失败,则其返回值如同shell执行了exit(127)一样。
 * 3,所有三个函数都执行成功,并且system的返回值是shell的终止状态,其格式已在waitpid中说明。
 *
 * shell的 -c选项告诉shell程序取下一个命令行参数作为标准输入,shell将对字符串
 * 进行语法分析,将它们分成命令行参数。
 * 调用_exit是为了防止任一标准IO缓冲区(由父进程复制到子进程)在子进程中被冲洗。
 */

/* apue英文版P248,中文版P202中介绍说在设置用户ID程序中调用system,执行其他程
 * 序,存在安全性漏洞,导致超级用户权限保存下来的问题注释中说,bash v2中不能运
 * 行此实例,经ubuntu 11.04 bash4.28实测,否定,不知原因???*/

/* 如果一个进程正以特殊的权限运行,它又想生成另一个进程执行另一个程序,则它应
 *应当直接fork和exec,且在fork后、exec之前改回普通用户权限。设置用户ID和设置组
 ID程序绝不应当调用system函数*/

/* 进程时间:
 *
 * 任一进程都可调用times函数以获得它自己及终止子进程的墙上时钟时间、用户CPU时
 *时间、系统CPU时间*/

#include	<sys/times.h>

struct tms {
        clock_t tms_utime; /* user CPU time */
        clock_t tms_stime; /* system CPU time */
        clock_t tms_cutime; /* user CPU time, terminated children */
        clock_t tms_cstime; /* system CPU time,terminated children */
};

clock_t times(struct tms *buf);

/* 第9章:进程关系
 */

/* 每个进程都有一个父进程(初始的内核进程并无父进程,也可以说其父进程就是自己
 * BSD终端登录,init进程读取/etc/ttys
 * Linux终端登录,init进程读取/etc/inittab,但upstart(ubuntu) init进程读取
 * /etc/init/tty*文件
 *
 * 传统的UNIX系统用户身份验证过程如下:
 * 系统自举时,内核创建进程为1的init进程,读取终端配置文件,对每一个允许登录的
 * 终端设备,init以空环境调用一次fork,它所生成的子进程执行(exec)getty程序。
 *
 * getty为终端设备调用open函数,以读写方式打开终端。输出login:   之类的信息。
 * 然后它以类似于下面的方式调用login程序。
 * execle("/bin/login", "login", "-p", username, (char *)0, envp);
 * getty以终端名和在gettytab中说明的字符串为login创建一个环境(envp)。
 * -p通知login保留传送给它的环境。
 *
 * login调用getpwnam取得相应用户的口令文件登录项,然后getpass,getpass再调用
 * crypt将用户键入的口令加密与该用户在阴影文件口令中登录项的pw_passwd字段相比相比较。
 *
 * 现代UNIX都支持PAM(可插入式身份验证模块)。
 * */

/* 如果用户登录正确,login将执行如下工作:
 * 当前工作目录更改为用户的起始目录(chdir)
 * 调用chown改变该终端的所有权,使登录用户称为它的所有者
 * 将对该终端设备的访问权改变成用户读和写
 * 调用setgid及initgroups设置进程的组ID
 * 用login所得到的所有信息初始化环境
 * login进程改变为登录用户的用户ID(setuid)并调用该用户的登录shell,如下:*/

execl("bin/sh", "-sh", (char *)0);
/* argv[0]的第一个字符'-'是一个标志,表示该shell被调用为登录shell。
 */

#include	<unistd.h>
pid_t getpgrp(void);

pid_t getpgid(pid_t pid); /* 若pid为0,则返回调用进程的进程组ID。 */
int setpgid(pid_t pid, pid_t pgid);
/* setpgid函数将pid进程的进程组ID改为pgid。如果两个参数相等,则pid变成进程组组长。
 * 如果pid是0,则使用调用者的进程ID。如果pgid是0,则由pid指定的进程ID将用作进程组ID。
 * 一个进程只能为它自己或它的子进程设置进程组ID。*/

pid_t setsid(void);
/* 如果调用此函数的进程不是一个进程组的组长,则此就创建一个进程
 * 结果将发生以下三件事:
 * 该进程变成会话首进程
 * 该进程成为一个进程组的组长进程。
 * 该进程没有控制终端。如果在setsid之前该进程有一个控制终端,那么这种联系会切
那么这种联系会切断。 */

pid_t getsid(pid_t pid);
/* 如果pid是0,getsid返回调用进程的会话首进程的进程组ID。
 * Linux之外有些系统如果pid不属于调用者所在的会话,则产生EPRM错误。*/

/* 建立与控制终端连接的会话首进程被称为控制进程(controlling process)。
 * 一个会话中的几个进程组可分成一个前台进程组 (foreground process group),以及
 * 一个或几个后台进程组 (background process group)。
 * 无论何时键入终端的中断键 (Ctrl+C) 或终端的退出键 (Ctrl+\),就会将信号发送哦
发送给前台进程组的所有进程。
 */

pid_t tcgetpgrp(int filedes);

int tcsetpgrp(int filedes, pid_t pgrpid);
/* pgrpid的值应当是在同一会话中的一个进程组的ID
 * filedes必须引用该会话的控制终端。*/

pid_t tcgetsid(int filedes);

/* 第10章:信号
 */

/* 信号是一种软件中断,它提供了一种处理异步事件的方法。
 * kill函数对信号编号0有特殊的应用,POSIX.1将此种信号编号值称为空信号。
 * 如果signo参数是0,则kill仍执行正常的错误检查,但不发送信号,常被用来确定一
确定一个特定进程是否仍旧存在。如果并不存在,则kill返回-1,并将errno设为 ESRCH。

 * kill(2)函数可将信号发送给另一个进程或进程组。但!接收信号进程和发送信号进程
 * 的所有者必须相同,或者发送信号进程的所有者为超级用户。SIGKILL和SIGSTOP信号
 * 不能被忽略或者被捕捉。
 * signal函数由ISO C定义,因为ISO C不涉及多线程、进程组以及终端I/O,所以它对信
 * 号的定义不明确,最好使用sigaction函数。
 */


#include	<signal.h>

void (*signal(int signum, void (*func)(int)))(int);
/* 若成功返回信号以前的处理配置(之前的信号处理程序的指针),若出错则返回SIG_ERR。
 *
 * 子进程继承父进程的信号处理方式,exec函数将原先设置要捕捉的函数都更改为它们的默认动作。
 */

if (signal(SIGINT, SIG_IGN) != SIG_IGN)
{
        signal(SIGINT, sig_int);        /* 仅当信号当前未被忽略时,进程才会捕捉它们。 */
}

/* 早期UNIX的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信
 * 信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno被设置为EINTR。
 *
 * 早期UNIX版本中的一个问题是:在进程每次接收到信号并对其进行处理时,随即将信
 *信号动作复位为默认值。
 *
 * 必须区分系统调用和函数!当捕捉到某个信号时,被中断的是内核中的系统调用。
 *
 * 低速系统调用包括:
 * 1,在读某些类型的文件时(管道,终端设备,网络设备)时,如果数据并不存在则可
 * 可能会使调用者永远阻塞。
 * 2,在写这些类型的文件时,如果不能立即接受这些数据,也可能会使调用者永远阻塞。
 * 3,打开某些类型的文件,在某中条件发生之前(如打开终端设备,它要等待直到调制
 调制解调器应答了电话)。
 * 4,pause和wait函数
 * 5,某些ioctl操作和某些进程间通信函数。
 *
 * FreeBSD,Linux,Mac OS的默认方式是重启动由信号中断的系统调用,Solaris 9默认EINTR。
 *
 */
/*
 * 不可重入函数原因:
 * 1,已知它们使用静态数据结构。
 * 2,它们调用malloc 或 free
 * 3,他们是标准I / O函数。
 * 各种wait函数都能改变errno值。
 */

/* 如果特地设置信号SIGCLD SIGCHLD的捕捉配置为SIG_IGN,则调用进程的子进程不产生
 * 僵死进程。(FreeBSD除外)
 *
 * 在产生了信号时,内核通常在进程表中设置一个某种形式的标志——递送。
 * 在信号产生 (generation) 和递送 (delivery)之间的时间间隔内,我们称信号是味觉
 未决的 (pending) 。
 *
 * 内核在递送一个原来被阻塞的信号给进程时(而不是在产生该信号时),才决定对他
 对它的处理方式。
 * POSIX.1的Rationale建议:在其他信号之前递送与进程当前状态有关的信号,如SIGSEGV。
 *
 */


#include	<signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
/*
 * kill的pid参数:
 * pid > 0 	将该信号发送给进程ID为pid的进程
 * pid = 0 	将信号发送给与发送进程属于同一进程组的所有进程。但不包括实现
 * 定义的系统进程集。(包括内核进程和 init(pid 1) )
 * pid == -1 	将该信号发送给发送进程有权限向他们发送信号的系统上的所有进程。
 * pid < 0 	发送给其进程组ID等于pid的绝对值。
 *
 * SIGCONT例外,进程可将它发送给属于同一会话的任何进程。
 * */

#include	<unistd.h>
unsigned int alarm(unsigned int seconds);
/* 返回值为0或以前设置的闹钟时间的余留秒数。
 * 每个进程只能有一个闹钟时间。
 */

int pause(void);   /* 只有执行了一个信号处理程序并返回时才返回,值为-1,并将errno设置为EINTR。*/

/* 信号处理程序中,如果该信号中断了其他信号处理程序,且使用longjump避免信号与
 * pause等的竞争条件,则有可能会提早终止其他信号处理程序。
 *
 * 如果要对I/O操作设置时间限制,则可使用longjmp或者使用select和poll函数。
 */

sigset_t set;
*set |= 1 << (signo - 1);  /* sigaddset的一种实现 */
*set &= ~(1 << (signo - 1)); /* sigdelset的一种实现 */

int sigprocmask(int how, sigset_t *restrict set, sigset_t *restrict oset);
/* SIG_BLOCK ,当前信号屏蔽字与set指向信号集的屏蔽字的并集。
 * SIG_UNBLOCK,当前信号屏蔽字与 set指向信号补集 的并集。
 * 而SIG_SETMASK,进程的屏蔽字将被set所指向的信号集的值代替。
 * sigprocmask仅为单线程的进程定义。
 */


#include	<signal.h>

int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict buf);

struct sigaction {
        void     (*sa_handler)(int);
        void     (*sa_sigaction)(int, siginfo_t *, void *);
        sigset_t   sa_mask;
        int        sa_flags;
        void     (*sa_restorer)(void);
};
/* sa_mask字段说明了一个信号集,在调用信号捕捉函数之前,这一信号集要加到进程的
进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复原。
 *

           SA_NOCLDSTOP
                  If  signum  is SIGCHLD, do not receive notification when child processes stop (i.e., when they receive one of SIGSTOP, SIGTSTP,
                  SIGTTIN or SIGTTOU) or resume (i.e., they receive SIGCONT) (see wait(2)).  This flag is only  meaningful  when  establishing  a
                  handler for SIGCHLD. 进程停止时不产生SIGCHLD信号,进程终止时仍然产生SIGCHLD信号。

           SA_NOCLDWAIT (since Linux 2.6)
                  If  signum  is  SIGCHLD,  do  not transform children into zombies when they terminate.  See also waitpid(2).  This flag is only
                  meaningful when establishing a handler for SIGCHLD, or when setting that signal's disposition to SIG_DFL.

                  If the SA_NOCLDWAIT flag is set when establishing a handler for SIGCHLD, POSIX.1 leaves it unspecified whether a SIGCHLD signal
                  is  generated when a child process terminates.  On Linux, a SIGCHLD signal is generated in this case; on some other implementa-
                  tions, it is not.

           SA_NODEFER
                  Do not prevent(阻塞) the signal from being received from within its own signal handler.  This flag is only meaningful when establish-
                  ing a signal handler.  SA_NOMASK is an obsolete, nonstandard synonym for this flag. 对应于早期的不可靠信号。

           SA_ONSTACK
                  Call  the  signal handler on an alternate signal stack provided by sigaltstack(2).  If an alternate stack is not available, the
                  default stack will be used.  This flag is only meaningful when establishing a signal handler.

           SA_RESETHAND
                  Restore the signal action to the default state once the signal handler has been called.  This  flag  is  only  meaningful  when
                  establishing a signal handler.  SA_ONESHOT is an obsolete, nonstandard synonym for this flag. 对应于早期不可靠信号。

           SA_RESTART
                  Provide  behavior compatible with BSD signal semantics by making certain system calls restartable across signals.  This flag is
                  only meaningful when establishing a signal handler.  See signal(7) for a discussion of system call restarting.
		  XSI扩展规定,除非说明了SG_RESTART标志,否则sigaction函数不再重启动被中断的系统调用。

	   SA_SIGINFO (since Linux 2.2)
                  The signal handler takes 3 arguments, not one.  In this case, sa_sigaction should be set instead of sa_handler.  This  flag  is
                  only meaningful when establishing a signal handler.

系统支持实时扩展时,由SA_SIGINFO设置的sa_sigaction信号处理程序将导致信号可靠的排队。
 */

/* Freebsd和Mac OS中,setjmp和longjmp函数保存和恢复信号屏蔽字,但其中的_setjmp
 * 和_longjmp与Linux和Solaris 9中的setjmp,longjmp不执行此操作。
 */

/* 在信号处理程序中应当使用sigsetjmp与siglongjmp */


#include	<setjmp.h>

int sigsetjmp(sigjmp_buf env, int savemask);
/* 直接调用返回0,从siglongjmp返回则返回非0值,如果savemask非0,则sigsetjmp
 * 在env中保存进程的当前信号屏蔽字。
 * 调用siglongjmp时,如果sigsetjmp已经保存了env,则siglongjmp从其中恢复保存的信
保存的信号屏蔽字。
*/
int siglongjmp(sigjmp_buf env, int val);

/* 程序清单10-14
 * 在信号处理程序中调用siglongjmp,应当使用如下技术:
 * 仅在调用sigsetjmp后才将变量canjump设置为非0值。
 * 在信号处理程序中检测次变量,仅当它为非0值时才调用siglongjmp。
 * 这提供了一种保护机制,使得在jmbuf(跳转缓冲)尚未由sigsetjmp初始化时,防止
 * 调用信号处理程序。
 *
 * 数据类型 sig_atomic_t,由ISO C标准定义,在写这种类型的变量时不会被中断。
 * 这意味着在具有虚拟存储器的系统上这种变量不会跨越页边界,可用一条机器指令。
 * 这种类型的变量总是包括ISO类型修饰符volatile,其原因是:该变量将由两个不同哦
两个不同的控制线程——main函数和异步执行的信号处理程序访问。
*/

int sigsuspend(const sigset_t *sigmask);         /* 始终返回-1,errno设置为EINTR */


#include	<stdlib.h>

void abort(void);
/* POSIX.1要求如果abort调用终止进程,则它对所有打开的标准I/O流的效果应当与进程终止前对每个流调用fclose相同。 POSIX.1也说明abort并不理会进程对此信号的阻塞和忽略。*/

/* POSIX.1说明:若父进程捕捉SIGCHLD信号,那么正在执行system函数时,应当阻塞对
 * 父进程递送SIGCHLD信号。否则,当system函数创建的子进程结束时,system的调用者
 * 可能错误认为,它自己的一个子进程已经结束了。
 */

/* system函数的实现需要注意:sigation忽略掉父进程的SIGINT,SIGQUIT信号,
 * sigprocmask block掉SIGCHLD,子进程在exec前恢复SIGINT,SIGQUIT默认处理程序,
 * 恢复原来的mask值。然后父进程调用wait系列函数获取状态,恢复SIGINT,SIGQUIT,
 * mask值,并返回子进程退出状态。
 *
 * system返回值是shell终止状态,但shell的终止状态并不总是执行命令字符串进程的
 * 终止状态。bash中有意个特性,如果用退出键或其他信号杀死执行命令进程后,其中
 *终止状态是128+信号编号。该信号终止了正在执行的命令。
 * 例如 sh -c "sleep 30",后,用ctrl+c中断掉,返回状态为130。
 *
 * 当shell本身异常终止时,system的返回值才报告一个异常终止。
 */

/* POSIX.1认为有6个信号与作业控制有关。
 *
 * SIGCHLD 	子进程已停止或终止。
 * SIGCONT 	如果进程已停止,则使其继续运行。
 * SIGSTOP 	停止信号(不能被捕捉或忽略)。
 * SIGTSTP 	交互式停止信号。
 * SIGTTIN 	后台进程组成员读控制终端。当输入Ctrl+z时(挂起),SIGTSTP背诵
 被送至前台进程组的所有进程。
 * SIGTTOU 	后台进程组成员写到控制终端
 *
 * 除SIGCHLD以外,大多数应用程序并不处理这些信号,交互式SHELL通常做所有工作。
 * 作业控制信号有某种交互作用。当对一个劲成产生四种停止信号(SIGTSTP, SIGSTOP,
 * SIGTTIN或SIGTTOU)中的任意一种时,对同一进程的任一未决SIGCONT信号将被丢弃。
 * 与此类似,对一个进程产生SIGCONT信号时,对同一进程的任一未决停止信号将被丢弃
 */

/*  某些系统提供数组extern char *sys_siglist[];
 *  数组下表是信号编号,给出一个指向信号字符串名字的指针。 */

#include	<signal.h>
void psignal(int signo, const char *msg);       /* 类似于perror */
#include	<string.h>
char *strsignal(int signo);                     /* 返回该信号说明的字符串 */

 

Avatar_small
λ 说:
2011年7月16日 08:48

cow 真粗鄙 - -

Avatar_small
星海 说:
2011年7月16日 13:18

-___-

很激情的。。。准备再激情一下。。。。。

Avatar_small
전설 서구 说:
2021年3月02日 15:44

Very nice article, I enjoyed reading your post, very nice share, I want to twit this to my followers. Thanks!. 123 movies

Avatar_small
RBSE 12th Question P 说:
2021年10月20日 16:59

RBSE Model Paper 2022 for 12th Class the Preparation of Upcoming Senior Secondary Final Exam are Available Here for Arts, Science, Commerce Download in PDF Format, These RBSE Question Papers RBSE 12th Question Paper 2022 are Important Resources and Important for the Preparation of Upcoming Rajasthan Board 12th Class Exam 2022, Boardmodelpaper.com Provide Rajasthan Board 12th Class Latest and Last Year Exam Study Material for Syllabus, Question Paper, etc, in Hindi, English Medium Pdf Format.


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter