### 实验环境介绍
* gcc:4.8.5
* glibc:glibc-2.17-222.el7.x86_64
* os:Centos7.4
* kernel:3.10.0-693.21.1.el7.x86_64
函数fork
* 子进程和父进程继续执行fork之后,子进程是父进程的副本。子进程获得父进程数据空间、堆和栈的副本。父进程和子进程不共享这些存储空间。父进程和子进程共享正文段
* 父进程和子进程之间使用写时复制技术。父进程数据段、堆、栈由父进程和子进程共享,如果父进程和子进程中的任一一个试图修改这些区域,则内核只为哪块内存制作一个副本,通常是虚拟存储系统中的一“页”
* 测试代码:
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#define err_sys(fmt, arg...) \
do { \
printf(fmt, ##arg);\
printf("\nerrno:%d %s\n", errno, strerror(errno));\
exit(1);\
} while (0)
int globvar = 6; /* external variable in initialized data */
char buf[] = "a write to stdout\n";
int
main(void)
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
err_sys("write error");
printf("before fork\n"); /* we don't flush stdout */
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { /* child */
globvar++; /* modify variables */
var++;
} else {
sleep(2); /* parent */
}
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
var);
exit(0);
}
[root@localhost part_8]# ./fork
a write to stdout
before fork # 这里由于printf是输出到标准输出,且标准输出指向终端设备(此时缓冲类型为行缓冲),所以父进程直接刷新了缓冲区,子进程得到的缓冲区为空
pid = 7176, glob = 7, var = 89
pid = 7175, glob = 6, var = 88
[root@localhost part_8]#
[root@localhost part_8]# ./fork > fork.out
[root@localhost part_8]# cat fork.out
a write to stdout
before fork
pid = 7178, glob = 7, var = 89
before fork # 这里由于printf是输出到标准输出,且标准输出指向文件(此时缓冲类型是全缓冲),所以父进程没有刷新了缓冲区,子进程得到的缓冲区和父进程一样,所以会有两次输出
pid = 7177, glob = 6, var = 88
进程文件共享
- 在父进程打开文件后进行fork,进程间对文件的共享如图
- 如果父子进程写同一描述符指向的文件,但又没有任何形式的同步,那么他们的输出会相互混合(描述符是fork之前打开)
fork之后,子进程继承父进程的属性
- 继承的属性包括:
- 实际用户ID、实际组ID、有效用户ID、有效组ID
- 附属组ID
- 进程组ID
- 会话ID
- 控制终端
- 设置用户ID标志和设置组ID标志
- 当前工作目录
- 根目录
- 文件模式创建屏蔽字(umask)
- 信号屏蔽和注册
- 对任一打开文件描述符的执行时关闭(文件描述符标志close-on-exec)
- 环境
- 连接的共享存储段
- 存储映像
- 资源限制
- 父子进程之间的区别:
- fork的返回值不同
- 进程ID不同、父进程ID不同
- 子进程的tms_utime、tms_stime、tms_cutime和tms_ustime的值设置为0(后面介绍)
- 子进程不继承父进程设置的文件锁
- 子进程的未处理的闹钟被清除
- 子进程的未处理信号集为空集
子进程从fork返回后立即调用exec,这个组合操作叫做spawn。UNIX系统一般把这两个操作分开,因为很多场合需要单独使用fork,不跟随exec,使得在fork和exec之间可以更改自己的属性。如I/O重定向、用户ID、信号注册(第15章再来讨论)
vfork函数
- vfork与fork的不同
- vfork保证子进程先运行
- 子进程调用exec和exit之后,父进程才运行。在exec之前,子进程和父进程共享地址空间
- 测试代码1:
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#define err_sys(fmt, arg...) \
do { \
printf(fmt, ##arg);\
printf("\nerrno:%d %s\n", errno, strerror(errno));\
exit(1);\
} while (0)
int globvar = 6; /* external variable in initialized data */
int
main(void)
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
printf("before vfork\n"); /* we don't flush stdio */
if ((pid = vfork()) < 0) {
err_sys("vfork error");
} else if (pid == 0) { /* child */
globvar++; /* modify parent's variables */
var++;
_exit(0); /* child terminates */
}
/* parent continues here */
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
var);
exit(0);
}
[root@localhost part_8]# ./vfork
before vfork
pid = 7305, glob = 7, var = 89 # 父子进程共享地址空间
[root@localhost part_8]# ./vfork > vfork.out
[root@localhost part_8]# cat vfork.out
before vfork
pid = 7307, glob = 7, var = 89 # 父子进程共享地址空间
- 测试代码2
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#define err_sys(fmt, arg...) \
do { \
printf(fmt, ##arg);\
printf("\nerrno:%d %s\n", errno, strerror(errno));\
exit(1);\
} while (0)
int globvar = 6; /* external variable in initialized data */
int
main(void)
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
printf("before vfork\n"); /* we don't flush stdio */
if ((pid = vfork()) < 0) {
err_sys("vfork error");
} else if (pid == 0) { /* child */
globvar++; /* modify parent's variables */
var++;
exit(0); /* child terminates */ # 换个函数试试
}
/* parent continues here */
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
var);
exit(0); 、、
}
[root@localhost part_8]# ./vfork
before vfork
pid = 7316, glob = 7, var = 89 # 父子进程共享地址空间
[root@localhost part_8]# ./vfork > vfork.out
[root@localhost part_8]# cat vfork.out
before vfork
pid = 7318, glob = 7, var = 89 # 父子进程共享地址空间
函数exit
- 五种正常终止方式
- main函数中return
- 调用exit含糊是
- 调用_exit或者_Exit函数
- 进程的最后一个线程return,但是该线程的返回值不作为进程的返回值。而是以0作为返回值
- 进程的最后一个线程调用pthread_exit函数,和前面一样
- 3种异常终止方式
- 调用abort,产生SIGABRT信号
- 接收到某些信号
- 最后一个线程对取消请求做出响应。
- 如果父进程在子进程终止前提前终止,子进程的父ID为1
- 父进程可以调用wait或者waitpid得到已经终止的子进程的信息
- 进程id
- 进程终止状态
- 进程使用的cpu总量
- 一个已经终止、但是父进程没有对其进行善后处理(使用wait函数之类的,获取终止子进程的有关信息、释放它所占用的资源)的进程成为僵尸进程zombie,ps命令中进程状态显示为Z的进程
wait和waitpid
- 如何进行子进程的回收测试代码:
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>
#define err_sys(fmt, arg...) \
do { \
printf(fmt, ##arg);\
printf("\nerrno:%d %s\n", errno, strerror(errno));\
exit(1);\
} while (0)
void pr_exit(int status);
int
main(void)
{
pid_t pid;
int status;
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
exit(7);
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
abort(); /* generates SIGABRT */
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
status /= 0; /* divide by 0 generates SIGFPE */
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
exit(0);
}
void
pr_exit(int status)
{
if (WIFEXITED(status))
printf("normal termination, exit status = %d\n",
WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number = %d%s\n",
WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status) ? " (core file generated)" : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n",
WSTOPSIG(status));
}
[root@localhost part_8]# ./zombie
normal termination, exit status = 7
abnormal termination, signal number = 6
abnormal termination, signal number = 8
- 如果父进程不行等子进程,让子进程单独运行,可以以下实现
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>
#define err_sys(fmt, arg...) \
do { \
printf(fmt, ##arg);\
printf("\nerrno:%d %s\n", errno, strerror(errno));\
exit(1);\
} while (0)
int
main(void)
{
pid_t pid;
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { /* first child */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0)
exit(0); /* parent from second fork == first child */
/*
* We're the second child; our parent becomes init as soon
* as our real parent calls exit() in the statement above.
* Here's where we'd continue executing, knowing that when
* we're done, init will reap our status.
*/
sleep(2);
printf("second child, parent pid = %ld\n", (long)getppid());
exit(0);
}
if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
err_sys("waitpid error");
/*
* We're the parent (the original process); we continue executing,
* knowing that we're not the parent of the second child.
*/
exit(0);
}
result:
[root@localhost part_8]# ./fork2
[root@localhost part_8]# second child, parent pid = 1
函数exec
- exec函数并不创建新的进程,只是用新程序替换了进程的正文段、数据段、堆栈
- 基本源于:exec、exit、wait、exec
- 新程序继承调用进程的下列属性:
- 进程ID和父进程ID
- 实际用户ID和实际组ID
- 附属组ID
- 进程组ID
- 会话ID
- 控制终端
- 闹钟上尚余留的时间
- 当前工作目录
- 根目录
- 文件模式创建屏蔽字(umask)
- 文件锁
- 进程信号屏蔽
- 未处理信号
- 资源限制
- nice
- tms_utime、tms_stime、tms_cutime以及tms_cstime值
- exec族函数之间的关系
更改用户ID和更改组ID
* 访问一个文件是基于用户ID和组ID的,当程序需要增加特权,或需要访问当前并不允许访问的资源时,我们需要更改自己的用户ID和组ID
* 更改用户ID的规则
* 如果进程具有超级用户权,则setuid函数将实际用户ID、有效用户ID以及保存的设置用户ID设置为uid
* 如果没有超级用户权,但uid等于实际用户id或保存的设置用户,则setuid只有将有效用户用户id设置为uid。不更改实际用户id和保存的设置用户id
* 测试代码如下:
[manjingliu@localhost part_8]$ ./uid
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdarg.h> /* ISO C variable aruments */
#include <string.h>
#define MAXLINE 4096
static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
char buf[MAXLINE];
vsnprintf(buf, MAXLINE-1, fmt, ap);
if (errnoflag)
snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
strerror(error));
strcat(buf, "\n");
fflush(stdout); /* in case stdout and stderr are the same */
fputs(buf, stderr);
fflush(NULL); /* flushes all stdio output streams */
}
void
err_sys(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, errno, fmt, ap);
va_end(ap);
// exit(1);
}
void pr_exit(int status);
int
main(void)
{
uid_t uid = 0;
int ret = setuid(uid);
if (!ret)
printf("set id = %d\n", uid);
else
err_sys("set root");
uid = 1000;
ret = setuid(uid);
if (!ret)
printf("set id = %d\n", uid);
else
err_sys("set manjingliu");
uid = 1002;
ret = setuid(uid);
if (!ret)
printf("set id = %d\n", uid);
else
err_sys("set 1002");
exit(0);
}
result:
[manjingliu@localhost part_8]$ sudo ./uid
set id = 0
set id = 1000
set 1002: Operation not permitted
[manjingliu@localhost part_8]$ ./uid
set root: Operation not permitted
set id = 1000
set 1002: Operation not permitted
[manjingliu@localhost part_8]$ su
Password:
[root@localhost part_8]# ./uid
set id = 0
set id = 1000
set 1002: Operation not permitted
- 更改三个id的方法
函数setreuid和setregid
- 交换实际用户id和有效用户id的值
- 规则
- 一个非特权用户总能交换实际用户ID和有效用户ID。这就允许一个设置用户ID程序可以交换成用户的普通权限,以后又可以再次交换为设置用户ID权限
- 一个非特权用户总能交换实际用户ID和有效用户ID。这就允许一个设置用户ID程序可以交换成用户的普通权限,以后又可以再次交换为设置用户ID权限
解释器文件
- 忽略
函数system
- system函数的一种实现
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
int
system(const char *cmdstring) /* version without signal handling */
{
pid_t pid;
int status;
if (cmdstring == NULL)
return(1); /* always a command processor with UNIX */
if ((pid = fork()) < 0) {
status = -1; /* probably out of processes */
} else if (pid == 0) { /* child */
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127); /* execl error */
} else { /* parent */
while (waitpid(pid, &status, 0) < 0) {
if (errno != EINTR) {
status = -1; /* error other than EINTR from waitpid() */
break;
}
}
}
return(status);
}
进程会计
- 忽略
用户标识
* 获取登录名,如果调用此函数的进程没有连接到用户登录时所用的终端,则失败。一般这些进程都是守护进程
进程调度
- 测试代码如下
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdarg.h> /* ISO C variable aruments */
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#if defined(MACOS)
#include <sys/syslimits.h>
#elif defined(SOLARIS)
#include <limits.h>
#elif defined(BSD)
#include <sys/param.h>
#endif
#define MAXLINE 4096
static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
char buf[MAXLINE];
vsnprintf(buf, MAXLINE-1, fmt, ap);
if (errnoflag)
snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
strerror(error));
strcat(buf, "\n");
fflush(stdout); /* in case stdout and stderr are the same */
fputs(buf, stderr);
fflush(NULL); /* flushes all stdio output streams */
}
void
err_sys(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, errno, fmt, ap);
va_end(ap);
// exit(1);
}
void
err_quit(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(0, 0, fmt, ap);
va_end(ap);
exit(1);
}
unsigned long long count;
struct timeval end;
void
checktime(char *str)
{
struct timeval tv;
gettimeofday(&tv, NULL);
if (tv.tv_sec >= end.tv_sec && tv.tv_usec >= end.tv_usec) {
printf("%s count = %lld\n", str, count);
exit(0);
}
}
int
main(int argc, char *argv[])
{
pid_t pid;
char *s;
int nzero, ret;
int adj = 0;
setbuf(stdout, NULL);
#if defined(NZERO)
nzero = NZERO;
#elif defined(_SC_NZERO)
nzero = sysconf(_SC_NZERO);
#else
#error NZERO undefined
#endif
printf("NZERO = %d\n", nzero);
if (argc == 2)
adj = strtol(argv[1], NULL, 10);
gettimeofday(&end, NULL);
end.tv_sec += 10; /* run for 10 seconds */
if ((pid = fork()) < 0) {
err_sys("fork failed");
} else if (pid == 0) { /* child */
s = "child";
printf("current nice value in child is %d, adjusting by %d\n",
nice(0)+nzero, adj);
errno = 0;
if ((ret = nice(adj)) == -1 && errno != 0)
err_sys("child set scheduling priority");
printf("now child nice value is %d\n", ret+nzero);
} else { /* parent */
s = "parent";
printf("current nice value in parent is %d\n", nice(0)+nzero);
}
for(;;) {
if (++count == 0)
err_quit("%s counter wrap", s);
checktime(s);
}
}
result:
[manjingliu@localhost part_8]$ taskset -c 1 ./nice 20
NZERO = 20
current nice value in parent is 20
current nice value in child is 20, adjusting by 20
now child nice value is 39
parent count = 353444491
child count = 5220216
[manjingliu@localhost part_8]$ taskset -c 1 ./nice
NZERO = 20
current nice value in parent is 20
current nice value in child is 20, adjusting by 0
now child nice value is 20
child count = 180986051
parent count = 180957702
进程时间
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdarg.h> /* ISO C variable aruments */
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/times.h>
#define MAXLINE 4096
static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
char buf[MAXLINE];
vsnprintf(buf, MAXLINE-1, fmt, ap);
if (errnoflag)
snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
strerror(error));
strcat(buf, "\n");
fflush(stdout); /* in case stdout and stderr are the same */
fputs(buf, stderr);
fflush(NULL); /* flushes all stdio output streams */
}
void
err_sys(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, errno, fmt, ap);
va_end(ap);
// exit(1);
}
void
err_quit(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(0, 0, fmt, ap);
va_end(ap);
exit(1);
}
static void pr_times(clock_t, struct tms *, struct tms *);
static void do_cmd(char *);
void pr_exit(int status);
static void pr_times(clock_t, struct tms *, struct tms *);
static void do_cmd(char *);
int
main(int argc, char *argv[])
{
int i;
setbuf(stdout, NULL);
for (i = 1; i < argc; i++)
do_cmd(argv[i]); /* once for each command-line arg */
exit(0);
}
static void
do_cmd(char *cmd) /* execute and time the "cmd" */
{
struct tms tmsstart, tmsend;
clock_t start, end;
int status;
printf("\ncommand: %s\n", cmd);
if ((start = times(&tmsstart)) == -1) /* starting values */
err_sys("times error");
if ((status = system(cmd)) < 0) /* execute command */
err_sys("system() error");
if ((end = times(&tmsend)) == -1) /* ending values */
err_sys("times error");
pr_times(end-start, &tmsstart, &tmsend);
pr_exit(status);
}
static void
pr_times(clock_t real, struct tms *tmsstart, struct tms *tmsend)
{
static long clktck = 0;
if (clktck == 0) /* fetch clock ticks per second first time */
if ((clktck = sysconf(_SC_CLK_TCK)) < 0)
err_sys("sysconf error");
printf(" real: %7.2f\n", real / (double) clktck);
printf(" user: %7.2f\n",
(tmsend->tms_utime - tmsstart->tms_utime) / (double) clktck);
printf(" sys: %7.2f\n",
(tmsend->tms_stime - tmsstart->tms_stime) / (double) clktck);
printf(" child user: %7.2f\n",
(tmsend->tms_cutime - tmsstart->tms_cutime) / (double) clktck);
printf(" child sys: %7.2f\n",
(tmsend->tms_cstime - tmsstart->tms_cstime) / (double) clktck);
}
void
pr_exit(int status)
{
if (WIFEXITED(status))
printf("normal termination, exit status = %d\n",
WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number = %d%s\n",
WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status) ? " (core file generated)" : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n",
WSTOPSIG(status));
}
result:
normal termination, exit status = 0
[manjingliu@localhost part_8]$ ./pr_time "sleep 5" "date"
command: sleep 5
real: 5.01
user: 0.00
sys: 0.00
child user: 0.00
child sys: 0.00
normal termination, exit status = 0
command: date
Mon Aug 13 11:43:51 CST 2018
real: 0.00
user: 0.00
sys: 0.00
child user: 0.00
child sys: 0.01
normal termination, exit status = 0
[manjingliu@localhost part_8]$