fork、vfork、wait、waitpid、exec、exit,此外还介绍了:孤儿进程、僵尸进程、设置进程相关ID、system函数、进程会计、用户标识、进程调度、进程时间
进程ID:非负、唯一、可复用
0--对换进程--内核进程;1--init进程-用户进程;2--页守护进程--内核进程。
#include
pid_t getpid(void);//进程ID
pid_t getppid(void);//父进程ID
uid_t getuid(void);//用户ID
uid_t geteuid(void);//有效用户ID
gid_t getgid(void);//组ID
gid_t getegid(void);//有效组ID
练习程序如下:
#include
#include
int main()
{
printf("pid = %d\n", getpid());
printf("ppid = %d\n", getppid());
printf("uid = %d\n", getuid());
printf("euid = %d\n", geteuid());
printf("gid = %d\n", getgid());
printf("egid = %d\n", getegid());
exit(0);
}
结果:
#include
pid_t fork(void);
理解:调用一次,返回两次;子进程和父进程继续执行fork调用之后的命令;正文段由父子进程共享,数据段、堆段、栈段采用完全副本或者写时复制技术;父、子进程共享文件表项,所以就共享文件状态标志和文件偏移量。
fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中,对每个文件描述符来说,就好像执行了dup操作。父进程和子进程每个相同的打开描述符共享一个文件表项。如下图所示:
程序练习:
#include
#include
int g_val = 80;
int main()
{
int tmp = 5;
printf("before fork()\n");
pid_t pid;
if ((pid = fork()) < 0)
{
perror("fork() error");
exit(-1);
}
if (pid == 0)//子进程
{
g_val++;
tmp++;
}
else
{
sleep(2);
}
printf("pid = %d, ppid = %d, g_val = %d, tmp = %d\n", getpid(), getppid(), g_val, tmp);
exit(0);
}
结果:
分析:输出到屏幕是行缓冲,所以第一个printf语句遇到换行符就输出;输出到文件是全缓冲,所以子进程也会复制父进程的缓冲区,所以会输出两次printf;子进程对变量的改变不影响到父进程,因为子进程是在自己的地址空间中执行的;fork之后父、子进程谁先执行是不确定,取决于系统的调度算法
pid_t vfork(void);
理解:
子进程在父进程的地址空间中运行;vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行
程序练习:
#include
#include
int g_val = 80;
int main()
{
int tmp = 5;
printf("before fork()\n");
pid_t pid;
if ((pid = vfork()) < 0)
{
perror("fork() error");
exit(-1);
}
if (pid == 0)//子进程
{
g_val++;
tmp++;
_exit(0);
}
printf("pid = %d, ppid = %d, g_val = %d, tmp = %d\n", getpid(), getppid(), g_val, tmp);
exit(0);
}
结果:
分析:由于调用vfork后,子进程在父进程的地址空间中运行,所以改变了变量。
孤儿进程:父进程已经终止的进程。孤儿进程的父进程变为init进程,init进程是所有孤儿进程的父进程。
僵尸进程:子进程已终止,但父进程未获取终止子进程的终止状态,那么这些子进程就会变为僵尸进程,他们还占用着系统的资源。
清理僵尸进程的方法:调用kill杀死僵尸进程的父进程,则这些僵尸进程变为孤儿进程,进而他们的父进程变为init进程,init进程在调用wait获取这些孤儿进程的终止状态,则这些原来是僵尸进程,现在变为孤儿进程的进程就可以彻底被清理了。
具体见:孤儿进程与僵尸进程的总结(未细看,要回看)
#include
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
理解:
1.所有子进程都在运行,则阻塞
2.一个子进程终止,父进程取得终止状态,立刻返回
3.无子进程,出错
wait和waitpid区别:
1.waitpid可以等待一个特定的进程,pid=-1:任一子进程;pid=0:组ID等于调用进程ID的任一子进程;pid>0或pid<-1,等待该|pid|子进程
2.wait会一直阻塞,直到有一个子进程终止;而waitpid可以不阻塞,第三个参数options=WNOHANG,则非阻塞,options=0,则阻塞;
3.waitpid支持作业控制(?不懂)
练习程序:
#include
#include
void pr_exit(int status);//确定子进程终止状态类型
int main()
{
pid_t pid;
int status;
if ((pid = fork()) < 0)
{
perror("fork() error");
exit(-1);
}
if (pid == 0)
{
printf("I am child process, pid = %d\n", getpid());
exit(0);
}
else
sleep(1);//父进程睡眠1秒,确保子进程退出。可以不要这句,因为,下面的wait本来就会阻塞父进程直到子进程终止
wait(&status);
printf("I am parent process, pid = %d\n", getpid());
pr_exit(status);
if ((pid = fork()) < 0)
{
perror("fork() error");
exit(-1);
}
if (pid == 0)
{
printf("I am child process, pid = %d\n", getpid());
sleep(3);//睡眠3秒,测试父进程是否会阻塞
abort();
}
//waitpid(pid, &status, 0);//0会阻塞
waitpid(pid, &status, WNOHANG);//WNOHANG设置为非阻塞
printf("I am parent process, pid = %d\n", getpid());//非阻塞情况下,子进程在sleep(3),而父进程会继续向下执行
sleep(6);//确保子进程退出
pr_exit(status);
exit(0);
}
void pr_exit(int status)
{
if (WIFEXITED(status))
printf("normal exit, exit status = %d\n", WEXITSTATUS(status));
if (WIFSIGNALED(status))
printf("abnormal exit, signal number = %d\n", WTERMSIG(status));
if (WIFSTOPPED(status))
printf("child stoped, signal number = %d\n", WSTOPSIG(status));
}
结果:
分析:第二个子进程调用了abort(),应该是产生SIGABRT信号,应该是异常退出,所以WIFSIGNALED()应该为真,可结果却不是这样,这点和书上不同(为什么?)
#include
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */ );
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */ );
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
All seven return: −1 on error, no return on success
理解:
记忆方法:p:用文件名,f:用文件描述符,否则用路径名;l:用命令行参数列表,v:用命令行参数数组;e:用环境变量数组,否则用默认的environ;
重点:用p时,会在环境变量的路径名(PATH)里面找文件名,所以要确保路径名里面能找到要调用的程序文件名
execve是系统调用,剩下6个是库函数,最终都要调用execve
程序练习:
要调用的程序,回显命令行参数和环境变量:
#include
#include
int main(int argc, char *argv[])
{
extern char **environ;
char **penv;
penv = environ;
int i;
for (i = 0; i < argc; i++)
printf("argv[%d]=%s\n", i, argv[i]);//回显命令行参数
while (*penv != NULL)
{
printf("%s\n", *penv);//回显环境变量
penv++;
}
exit(0);
}
exec程序:
#include
#include
#include
int main()
{
char *env[] = {"name=zxin", "home=509", "path=/usr"};
pid_t pid;
if ((pid = fork()) < 0)
{
printf("fork() error\n");
exit(-1);
}
if (pid == 0)
{
if (execle("/home/zxin/ch8/echo", "arg1", "arg2", (char *)0, env) == -1)//执行新程序
{
printf("execle() error\n");
exit(-1);
}
}
if (waitpid(pid, NULL, 0) < 0)
{
printf("waitpid() error\n");
exit(-1);
}
if ((pid = fork()) < 0)
{
printf("fork() error\n");
exit(-1);
}
if (pid == 0)
{
if (execlp("echo", "echo", "arg2", "arg3", (char *)0) == -1)//执行新程序
{
printf("execle() error\n");
exit(-1);
}
}
if (waitpid(pid, NULL, 0) < 0)
{
printf("waitpid() error\n");
exit(-1);
}
exit(0);
}
结果如下:
分析:
调用execlp时一定要设置好PATH环境变量,过程如下:
因为调用的程序echo的位置为:/home/zxin/ch8/echo,所以要设置的环境变量PATH(也就是搜索路径)为:/home/zxin/ch8,以让程序能找到该文件。
设置过程如下:
1.查看环境变量PATH:echo $PATH,假如有上述路径,则不用设置,否则:
2.vim /etc/profile,再最后加上 export PATH="/home/zxin/ch8:$PATH",保存退出。
3.激活。source /etc/profile ,不报错则成功。
int system(const char *cmdstring);
system的实现中调用了fork、exec、waitpid
一种实现方法如下:
#include
#include
#include
int system(const char *s)
{
int status;
pid_t pid;
if (s == NULL)
return (1);
if ((pid = fork()) < 0)
status = -1;
else if (pid == 0)
{
execl("/bin/sh", "sh", "-c", s, (char *)0);//"-c"代表以后面的第一个参数为shell的输入的第一个参数
_exit(127);
}
else
{
while (waitpid(pid, &status, 0) < 0)
{
if (errno != EINTR)
{
status = -1;
break;
}
}
}
return (status);
}
int main()
{
if (system("date") < 0)
{
printf("system() error\n");
exit(-1);
}
if (system("who") < 0)
{
printf("system() error\n");
exit(-1);
}
exit(0);
}
结果:
#include
int setuid(uid_t uid);
int setgid(gid_t gid);
Both return: 0 if OK, −1 on error
root账号:3个ID全变为uid(或gid);一般账号:只改变有效用户ID为uid,同理gid.
#include
int seteuid(uid_t uid);
int setegid(gid_t gid);
Both return: 0 if OK, −1 on error
只更改有效用户ID和有效组ID
程序不想写了。。。(放假了,周末了,还在码代码!!!)
不是重点,自己看书了解概念就行了(写了一天代码,找了那么久的bug,又到周末了,实在不想写了。。。but明天还是要继续!!!)