linux进程

一、进程的含义

进程是运行中的程序,进程是操作系统资源分配的基本单位/最小单位。

  • 进程在内存,程序在硬盘(程序代码在硬盘,运行时转存到内存)
  • 同时运行2个程序时,它们是运行在物理内存的2个互不相关的地址上

例1:

// 001.c
#include 
#include 

int a;
int main()
{
    int b;
    printf("a is %p, b is %p\n", &a, &b);    // 查看a和b的地址
    while(1)
    {
        sleep(1);
    }
    return 0;
}
linux进程_第1张图片

说明:

Q1:2个程序运行时,为何a的地址是一样的?

显示的地址都是虚拟地址,而非物理地址。
CPU中的MMU(内存管理单元)会根据不同的进程(进程号),将虚拟地址映射到不同的物理地址上。

Q2:2个程序运行时,为何a的地址一样,而b的地址是不一样的?

a是全局变量,存放在数据段;b是局部变量,存放在栈段。
编译完后,数据段的全局变量的地址是固定好的;栈是运行时才会分配地址的,是动态的,因此每次运行时地址都是不同的。


二、进程终止方式

1、return

2、exit

// return与exit基本是等价的
return 0;
exit(0);
// 程序结束的时候,既可以用return 0; 也可以用exit(0)。
// 二者唯一的区别:
//    return 0; 返回时,会将程序控制权先交给程序(返回给函数)
//    exit(0); 退出时,会将程序控制权直接交给内核

3、_exit

exit与_exit的区别:
1、在不同的头文件中定义的。

// man exit
#include 

void exit(int status);

// man _exit
#include 

void _exit(int status);

2、exit在退出时,会先做一些清理操作(比如:关闭文件描述符),这样会使输出缓存中的数据被刷新一下;_exit不会做。

如:文件IO中,用到一个行缓存,没有用\0,暂时不会被打印出来的。用return或exit,就会将信息刷到屏幕上/文件中。而用_exit是不会做清理的,在退出时若没有打印出来,就一直不会被打印出来。


三、进程创建

1、fork函数

通过fork函数创建进程。
创建进程也可以叫fork一个进程。

Linux系统中的进程,都是由已有的进程派生出来的,即:父进程创建子进程。除了OS启动时,会创建一个init进程外,其他进程都是通过fork创建的。

vfork:最终也是调用了fork。

fork调用成功后,会派生出子进程;若调用失败,会返回-1。
子进程会继承父进程的几乎所有的数据(包括 数据段、代码段、用户id、主id、创建的文件描述符等),相当于父进程的一个拷贝(整个程序都会拷贝),唯一的区别是pid号不一样。

fork之后,就是2个进程在运行,2个进程是相互独立的。

CPU先调用哪个进程,是不能确定的。

例2:

// 002.c
#include 
#include 

int main()
{
    pid_t pid = 0;
    printf("=====\n");    // =====存在于数据段
    pid = fork();
    if(pid < 0) 
    {
        perror("fork");
        return -1;
    }
    printf("ok!\n");
    return 0;
}
linux进程_第2张图片
父进程与子进程都打印了ok!

fork之后,会产生2个返回值:若返回值>0,则是父进程;若返回值==0,则是子进程。

例3:

// 003.c
#include 
#include 

int main()
{
    pid_t pid = 0;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
linux进程_第3张图片

2、进程ID

进程创建后,就会得到一个唯一标识这个进程的ID(进程号)。

include 
include 

pid_t getpid(void);    // 得到当前调用函数的进程的id
pid_t getppid(void);    // 得到当前调用函数的进程的父进程的id

例4:

// 004.c
#include 
#include 
int main()
{
    pid_t pid;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent pid is %d\n", getpid(), getppid());
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        printf("parent pid is %d, parent parent pid is %d\n", getpid(), getppid());
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
linux进程_第4张图片
子进程先退出,退出时父进程的pid可以显示出来

bash fork得到父进程,父进程fork得到子进程。

linux进程_第5张图片
父进程先退出,child的父进程无从得知,child就变成孤儿进程;孤儿进程会被1号init进程收养

四、进程运行时

1、回收子进程

Q:若不想让子进程变为孤儿进程,该如何做?

(1)使用sleep()函数:等待多长时间

例5:

// 005.c
#include 
#include 
int main()
{
    pid_t pid;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent is %d\n", getpid(), getppid());
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        sleep(1);    // 睡1s,保证子进程先退出,然后再运行父进程
        printf("parent pid is %d, child is %d\n", getpid(), pid);
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
linux进程_第6张图片
先运行子进程,再运行父进程,保证子进程先结束.png

(2)使用getchar函数:等待键盘输入

例6:

// 006.c
#include 
#include 
int main()
{
    pid_t pid;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent is %d\n", getpid(), getppid());
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        getchar();    // 等待键盘输入
        printf("parent pid is %d, child is %d\n", getpid(), pid);
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
linux进程_第7张图片
子进程运行结束了,而父进程一直没有运行。此时,子进程将变为僵尸态
linux进程_第8张图片
ps aux,看到a.out为Z+,即为僵尸态

2、进程状态

linux进程_第9张图片
进程状态
  • 运行态:进程正在运行或在运行队列中等待
  • 不可中断等待态:进程必须等待某个事件完成,在等待中不可以被信号或者定时器唤醒
  • 可中断等待态:进程等待过程中可以被信号或定时器唤醒
  • 停止状态:进程收到信号,如:SIGSTOPSIGSTPSIGINSIGOUT
  • 僵尸态:子进程先于父进程结束,子进程变为僵尸态(子进程虽然结束了,但是资源仍然占用着),父进程需要对子进程资源进行回收即可解除僵尸态(调用wait函数)

3、wait函数

DESCRIPTION

All of these system calls are used to wait for state changes in a child of the calling process, and obtain information about the child whose state has changed. A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a signal. In the case of a terminated child, performing a wait allows the system to release the resources associated with the child; if a wait is not performed, then the terminated child remains in a "zombie" state.

If a child has already changed state, then these calls return immediately. Otherwise they block until either a child changes state or a signal handler interrupts the call (assuming that system calls are not automatically restarted using the SA_RESTART flag of sigaction(2)). In the remainder of this page, a child whose state has changed and which has not yet been waited upon by one of these system calls is termed waitable.

// man wait:
#include 
#include 

pid_t wait(int *status);   // 只能等到第一个子进程退出
// 父进程调用wait后,父进程就会阻塞,直到子进程运行结束,wait继续运行,此时父进程就对子进程的资源进行回收

pid_t waitpid(pid_t pid, int *status, int options);   // 可以等待指定的子进程退出

wait() and waitpid()

The wait() system call suspends execution of the calling process until one of its children terminates. The call wait(&status) is equivalent to:

waitpid(-1, &status, 0);

The waitpid() system call suspends execution of the calling process until a child specified by pid argument has changed state. By default, waitpid() waits only for terminated children, but this behavior is modifiable via the options argument.

例7:

// 007.c
#include 
#include 
int main()
{
    pid_t pid;
    pid_t pid2;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent is %d\n", getpid(), getppid());
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        getchar();
        pid2 = wait(0);   // 也可以不用返回值pid2。使用pid2意义不大,因为一旦wait返回,肯定子进程退出了;若wait不返回一直阻塞,说明一直在等待  
        // wait中是一个指针,此处不关心此指针,写一个0
        printf("pid2 is %d\n", pid2);
        printf("parent pid is %d, child is %d\n", getpid(), pid);
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
linux进程_第10张图片
一直在等待键盘输入,a.out是一个僵尸态
linux进程_第11张图片
ps aux,看到a.out为Z+,即为僵尸态
linux进程_第12张图片
wait返回的值就是子进程的进程id

例8:

// 008.c
#include 
#include 
#include 
#include 
int main()
{
    pid_t pid;
    pid_t pid2;
    int status;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent is %d\n", getpid(), getppid());
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        getchar();
        pid2 = wait(&status);
        printf("pid2 is %d\n", pid2);
        printf("exit status is %d\n", WEXITSTATUS(status));
        printf("parent pid is %d, child is %d\n", getpid(), pid);
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
linux进程_第13张图片
子进程在打印ok!并执行到return 0;时,会把0作为退出码;程序将状态码status通过宏定义的方式解析出来,返回exit status

五、子进程执行新程序

子进程若是拷贝父进程,是没有意义的;子进程必须执行新程序,才有创建的意义。

方法:使用exec函数族。

// man exec:
execl, execlp, execle, execv, execvp, execvpe - execute a file

#include 

extern char **environ;

int execl(const char *path, const char *arg, ...);    
// const char *path:只读的路径(相对路径/绝对路径)。
// const char *arg:参数,是字符串。它是可执行文件的名字。
// ...:可变参数
// 若无参数,写 NULL;不管有没有参数,最后结尾的必须是 NULL (为了告诉函数,参数到此为止)
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[]);
// 将参数写进一个数组中,因此不需要可变参数。结尾也是NULL。
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

例9:

(1)

// address.c
#include 
#include 

int a;
int main()
{
    int b;
    printf("a is %p, b is %p\n", &a, &b);
    return 0;
}

(2)

// 009.c
#include 
#include 
#include 
#include 
int main()
{
    pid_t pid;
    pid_t pid2;
    int status;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent is %d\n", getpid(), getppid());
        printf("it is child process\n");
        execl("./address", "address", NULL);
        printf("after execl\n");
    }
    else if(pid > 0)
    {
        getchar();
        printf("parent pid is %d, child is %d\n", getpid(), pid);
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
linux进程_第14张图片

结果说明:

1)运行009时,在子进程中,把address程序也运行了;
2)没有打印出after execl字符串,也只打印了一个ok!

当子进程调用execl后,子进程就将代码完全替换成了新程序:

  • 子进程的代码段替换成新程序的代码段
  • 原有的数据段和堆段还有其他数据都废弃掉,全部更新为新程序的数据段和堆段)
  • 唯一保留的是进程ID

并开始执行新程序。

因此,execl后面的代码都不起作用了,因此没有打印出after execl字符串,也只打印了一个ok!

你可能感兴趣的:(linux进程)