《APUE》笔记-第八章-进程控制

1.重点

fork、vfork、wait、waitpid、exec、exit,此外还介绍了:孤儿进程、僵尸进程、设置进程相关ID、system函数、进程会计、用户标识、进程调度、进程时间

2.进程标识

进程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);
}
结果:

《APUE》笔记-第八章-进程控制_第1张图片

3.fork

#include

pid_t fork(void);

理解:调用一次,返回两次;子进程和父进程继续执行fork调用之后的命令;正文段由父子进程共享,数据段、堆段、栈段采用完全副本或者写时复制技术;父、子进程共享文件表项,所以就共享文件状态标志和文件偏移量。

fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中,对每个文件描述符来说,就好像执行了dup操作。父进程和子进程每个相同的打开描述符共享一个文件表项。如下图所示:

《APUE》笔记-第八章-进程控制_第2张图片

程序练习:

#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);
}
结果:

《APUE》笔记-第八章-进程控制_第3张图片

分析:输出到屏幕是行缓冲,所以第一个printf语句遇到换行符就输出;输出到文件是全缓冲,所以子进程也会复制父进程的缓冲区,所以会输出两次printf;子进程对变量的改变不影响到父进程,因为子进程是在自己的地址空间中执行的;fork之后父、子进程谁先执行是不确定,取决于系统的调度算法

4.vfork

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后,子进程在父进程的地址空间中运行,所以改变了变量。

5.孤儿进程、 僵尸进程

孤儿进程:父进程已经终止的进程。孤儿进程的父进程变为init进程,init进程是所有孤儿进程的父进程。

僵尸进程:子进程已终止,但父进程未获取终止子进程的终止状态,那么这些子进程就会变为僵尸进程,他们还占用着系统的资源。

清理僵尸进程的方法:调用kill杀死僵尸进程的父进程,则这些僵尸进程变为孤儿进程,进而他们的父进程变为init进程,init进程在调用wait获取这些孤儿进程的终止状态,则这些原来是僵尸进程,现在变为孤儿进程的进程就可以彻底被清理了。

具体见:孤儿进程与僵尸进程的总结(未细看,要回看

6.wait、 waitpid

#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));
}
结果:

《APUE》笔记-第八章-进程控制_第4张图片

分析:第二个子进程调用了abort(),应该是产生SIGABRT信号,应该是异常退出,所以WIFSIGNALED()应该为真,可结果却不是这样,这点和书上不同(为什么?

7.exec

#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);
}
结果如下:

《APUE》笔记-第八章-进程控制_第5张图片
中间省略....行,结果最后:
《APUE》笔记-第八章-进程控制_第6张图片

分析:

调用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 ,不报错则成功。

8.system

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);
}
结果:

《APUE》笔记-第八章-进程控制_第7张图片

9.更改进程的用户ID和组ID

#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

程序不想写了。。。(放假了,周末了,还在码代码!!!)

10.解释器文件、 进程会计、 进程标识、 进程时间

不是重点,自己看书了解概念就行了(写了一天代码,找了那么久的bug,又到周末了,实在不想写了。。。but明天还是要继续!!!)



你可能感兴趣的:(linux环境编程)