进程创建和程序执行
一、进程记账
使用进程记账功能,内核会在每个进程终止时将一条记账信息写入系统级的进程记账文件。账单包含内核为该进程所维护的多种信息,包括终止状态以及进程消耗CPU时间。
1.打开和关闭进程记账功能
特权进程可利用下列函数打开和关闭进程记账功能。
#include<unistd.h>
int acct(const char *acctfile);
//acctfile:指定现有常规文件的路径名。
//记账文件通常路径名:/var/log/pacct或/usr/acconut/pacct
//关闭进程功能指定acctfile为NULL
例:打开关闭进程功能。
#include<stdio.h>
#include<unistd.h>
int main(char argc,char *argv[])
{
if(argc>2||strcmp(argv[1],"--help")==0))
usagErr("%s[file]\n");
if(acct(argv[1])==-1)
errExit("acct");
printf("Process accounting %s\n",(argv[1]==NULL?"disabled":"enabled");
exit(EXIT_SUCCESS);
打开进程功能,进程终止时会有一条acct记录写入记账文件。acct结构定义在头文件<sys/acct.h>
中:
typedef u_int16 comp_t;
struct acct{
char ac_flag;
u_int16_t ac_uid;
u_int16_t ac_gid;
u_int16_t ac_tty;
u_int32_t ac_btime;
comp_t ac_utime;
comp_t ac_stime;
comp_t ac_etime;
comp_t ac_mem;
comp_t ac_io;
comp_t ac_rw;
comp_t ac_minflt;
comp_t ac_majflt;
comp_t ac_swaps;
u_int32_t ac_exitcode;
#define ACC_COMM 16
char ac_comm[ACCT_COMM+1];
char ac_pad[10];
};
-
向记账文件写入信息可能会加速对磁盘空间的消耗,为了对进程记账行为加以控制,Linux系统提供了名为/proc/sys/kernel/acct的虚拟文件。
-
此文件包含3个值,按顺序分别定义如下参数:高水位、低水位、频率。默认值为4,2和30。
-
开启记账特性且磁盘空闲空间低于低水位百分百,将停止记账;升至高水位,则恢复记账。频率值规定两次检查空闲磁盘空间占比之间间隔时间。
例:下列程序显示进程记账文件记录中特定字段信息。
#include <pwd.h>
#include <grp.h>
#include <ctype.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/acct.h>
#include <limits.h>
#define TIME_BUF_SIZE 100
//返回与“uid”对应的名称,错误时返回NULL
char * userNameFromId(uid_t uid)
{
struct passwd *pwd;
pwd = getpwuid(uid);
return (pwd == NULL) ? NULL : pwd->pw_name;
}
//返回与“name”对应的UID,错误时返回-1
uid_t userIdFromName(const char *name)
{
struct passwd *pwd;
uid_t u;
char *endptr;
if (name == NULL || *name == '\0') /* On NULL or empty string */
return -1; /* return an error */
u = strtol(name, &endptr, 10); /* As a convenience to caller */
if (*endptr == '\0') /* allow a numeric string */
return u;
pwd = getpwnam(name);
if (pwd == NULL)
return -1;
return pwd->pw_uid;
}
//返回与“gid”对应的名称,错误时返回NULL
char * groupNameFromId(gid_t gid)
{
struct group *grp;
grp = getgrgid(gid);
return (grp == NULL) ? NULL : grp->gr_name;
}
// 返回与“name”对应的GID,错误时返回-1
gid_t groupIdFromName(const char *name)
{
struct group *grp;
gid_t g;
char *endptr;
if (name == NULL || *name == '\0') /* On NULL or empty string */
return -1; /* return an error */
g = strtol(name, &endptr, 10); /* As a convenience to caller */
if (*endptr == '\0') /* allow a numeric string */
return g;
grp = getgrnam(name);
if (grp == NULL)
return -1;
return grp->gr_gid;
}
static long long comptToLL(comp_t ct)
{
const int EXP_SIZE = 3; /* 3-bit, base-8 exponent */
const int MANTISSA_SIZE = 13; /* Followed by 13-bit mantissa */
const int MANTISSA_MASK = (1 << MANTISSA_SIZE) - 1;
long long mantissa, exp;
mantissa = ct & MANTISSA_MASK;
exp = (ct >> MANTISSA_SIZE) & ((1 << EXP_SIZE) - 1);
return mantissa << (exp * 3); /* Power of 8 = left shift 3 bits */
}
int main(int argc, char *argv[])
{
int acctFile;
struct acct ac;
ssize_t numRead;
char *s;
char timeBuf[TIME_BUF_SIZE];
struct tm *loc;
time_t t;
if (argc != 2 || strcmp(argv[1], "--help") == 0)
usageErr("%s file\n", argv[0]);
acctFile = open(argv[1], O_RDONLY);
if (acctFile == -1)
errExit("open");
printf("command flags term. user "
"start time CPU elapsed\n");
printf(" status "
" time time\n");
while ((numRead = read(acctFile, &ac, sizeof(struct acct))) > 0) {
if (numRead != sizeof(struct acct))
fatal("partial read");
printf("%-8.8s ", ac.ac_comm);
printf("%c", (ac.ac_flag & AFORK) ? 'F' : '-') ;
printf("%c", (ac.ac_flag & ASU) ? 'S' : '-') ;
printf("%c", (ac.ac_flag & AXSIG) ? 'X' : '-') ;
printf("%c", (ac.ac_flag & ACORE) ? 'C' : '-') ;
#ifdef __linux__
printf(" %#6lx ", (unsigned long) ac.ac_exitcode);
#else /* 许多其他实现都提供ac_stat*/
printf(" %#6lx ", (unsigned long) ac.ac_stat);
#endif
s = userNameFromId(ac.ac_uid);
printf("%-8.8s ", (s == NULL) ? "???" : s);
t = ac.ac_btime;
loc = localtime(&t);
if (loc == NULL) {
printf("???Unknown time??? ");
} else {
strftime(timeBuf, TIME_BUF_SIZE, "%Y-%m-%d %T ", loc);
printf("%s ", timeBuf);
}
printf("%5.2f %7.2f ", (double) (comptToLL(ac.ac_utime) +
comptToLL(ac.ac_stime)) / sysconf(_SC_CLK_TCK),
(double) comptToLL(ac.ac_etime) / sysconf(_SC_CLK_TCK));
printf("\n");
}
if (numRead == -1)
errExit("read");
exit(EXIT_SUCCESS);
}
进程记账文件格式备用版本,使用需打开内核选项CONFIG_BSD_PROCESS_aCCT_V3
。
struct acct_v3{
char ac_version;
u_int16_t ac_tty;
u_int32_t ac_exitcode;
u_int32_t ac_uid;
u_int32_t ac_gid;
u_int32_t ac_pid;
u_int32_t ac_ppid;
u_int32_t ac_btime;
float ac_etime;
comp_t ac_utime;
comp_t ac_stime;
comp_t ac_mem;
comp_t ac_io;
comp_t ac_rw;
comp_t ac_minflt;
comp_t ac_maiflt;
comp_t ac_swaps;
#define ACCT_COMM 16
char ac_comm[ACCT_COMM];
};
二、系统调用clone()
类似fork()和vfork(),Linux特有的系统调用clone()也能创建一个新进程。
clone()主要用于线程库的实现,由于clone()有损程序的可移植性,故而应避免在程序直接使用。
#include<sched.h>
int clone(int (*func)(void*),void *child_stack,int flags,void *func_arg,../pid_t *ptid,struct user_desc *tls,pid *ctid*/);
-
与fork()相同,由clone()创建的新进程几近父进程的翻板。
-
与fork()不同是,克隆生成的子进程继续运行时不以调用处为起点转而去调用以参数func所指定的函数,func又称子函数,调用子函数时的参数由func_arg指定。
-
经过适当转换,子函数可对该参数的含义自由解读,当函数func返回或是调用exit()之后,克隆产出的子进程就会终止,父进程可以通过wait()函数来等待克隆子进程。
-
克隆子进程共享父进程,所以不能使用父进程的栈,调用时需分配内存空间供子进程栈使用,将内存指针置于参数child_stack中。
-
ork()相当于仅设置flags为
SIGCHLD
的clone()调用,而vfork()则对应于设置的flags的clone():CLONE_VM|CLONE_VFORK|SIGCHLF
。
参数flags服务于双重目,低字节中存放着子进程的终止信号,子进程退出时其父进程将收到这一信号,flags也可能为0不会产生任何信号,参数flags的剩余字节则存放位掩码,控制clone()操作。如下表所示:
例:使用clone创建子进程。
#define _GNU_SOURCE
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sched.h>
#ifndef CHILD_SIG
#define CHILD_SIG SIGUSR1 //终止时生成的信号
#endif
// 克隆子对象的启动功能
static int childFunc(void *arg)
{
if (close(*((int *) arg)) == -1)
errExit("close");
return 0; /*子进程现在终止*/
}
int main(int argc, char *argv[])
{
const int STACK_SIZE = 65536; /* 克隆子级的堆栈大小 */
char *stack; /*堆栈缓冲区开始*/
char *stackTop; /* 堆栈缓冲区结束 */
int s, fd, flags;
fd = open("/dev/null", O_RDWR); /* 子进程将关闭此fd */
if (fd == -1)
errExit("open");
/* 若argc>1,则子级与父级共享文件描述符表 */
flags = (argc > 1) ? CLONE_FILES : 0;
/* 为子级分配堆栈 */
stack = malloc(STACK_SIZE);
if (stack == NULL)
errExit("malloc");
stackTop = stack + STACK_SIZE; /* 假设堆栈向下增长 */
/* 忽略CHILD_SIG,如果它是一个默认为
终止该过程;但不要忽略SIGCHLD(已忽略
默认情况下),因为这将阻止创建僵尸。 */
if (CHILD_SIG != 0 && CHILD_SIG != SIGCHLD)
if (signal(CHILD_SIG, SIG_IGN) == SIG_ERR) errExit("signal");
/* 创建子项;child在childFunc中开始执行)*/
if (clone(childFunc, stackTop, flags | CHILD_SIG, (void *) &fd) == -1)
errExit("clone");
/* 父进程等待孩子__WCLONE是
需要子进程用SIGCHLD以外的信号通知。*/
if (waitpid(-1, NULL, (CHILD_SIG != SIGCHLD) ? __WCLONE : 0) == -1)
errExit("waitpid");
printf("child has terminated\n");
/* 子级中文件描述符的close()是否影响父级? */
s = write(fd, "x", 1);
if (s == -1 && errno == EBADF)
printf("file descriptor %d has been closed\n", fd);
else if (s == -1)
printf("write() on file descriptor %d failed "
"unexpectedly (%s)\n", fd, strerror(errno));
else
printf("write() on file descriptor %d succeeded\n", fd);
exit(EXIT_SUCCESS);
}
1.因克隆生成的子进程而对waitpid()进行扩展
为等待由clone()产生的子进程,waitpid()、wait3()、wait4()的位掩码参数options可以包含如下值:
- _WCLONE:等待克隆子进程。
- _WALL:等待所有子进程,无论类型。
- _WNOTHREAD:默认情况等待类调用所等待的子进程,其父进程的范围遍及与调用者隶属同一线程组的任何进程。指定此标志则限制调用者只能等待自己的子进程。
waitid()不能使用上述标志。
三、进程的创建速度
下表显示不同方法创建进程的速度。
四、exec()和fork()对进程属性的影响