初识 Linux 进程

问题

strace 输出中的 execve(...) 究竟是什么?

进程生命周期

初识 Linux 进程_第1张图片

操作系统内部定义了进程的不同状态

Linux 进程基本概念

进程是 Linux 任务的执行单元,也是 Linux 系统资源的分配单元

每个 Linux 应用程序运行后由一个或多个进程构成

每个 Linux 进程可以执行一个或多个程序

Linux 进程有多种不同状态 (即:Linux 进程有不同的活法)

Linux 进程生命周期

初识 Linux 进程_第2张图片

可中断状态表示进程可以接收到内核给它发出的信号,不可中断状态表示进程不接收内核给它发出的信号

僵尸状态表示进程的生命周期已经结束了,但内核还残留着这个进程的资源

我们查看下当前的命令行所运行的进程

初识 Linux 进程_第3张图片

ps ax 命令查看每个运行进程的信息,grep "pts/2" 用来筛选当前命令行所运行的进程

第一列是每个进程的进程号,第三列是每个进程的状态

下图包含了进程的状态信息

初识 Linux 进程_第4张图片S 表示该进程处于可中断的阻塞状态,R 表示该进程处于运行或就绪状态

s 表示该进程是会话组长,+ 表示该进程在前台进程组里

Linux 进程必知必会

每一个进程都有一个唯一的标识 (PID)

每个进程都是由另一个进程创建而来 (即:父进程)

初识 Linux 进程_第5张图片

getpid() 用于获取当前进程的进程号,getppid() 用于获取当前进程的父进程号

Linux 进程树

整个 Linux 系统的所有进程构成一个树状结构

树根由内核自动创建即:IDLE (PID = 0)

系统中的第一个进程是 init / systemd (PID = 1)

0 号进程创建 1 号进程,1 号进程负责完成内核部分初始化工作

1 号进程加载执行初始化程序,演变为用户态 1 号进程

我们来查看下当前命令行进程的进程树

初识 Linux 进程_第6张图片

pstree 是查看当前系统进程树的命令,-p 参数会展示出每个进程的 pid,-s 参数表示指定查看某个进程,$$ 代表当前进程的 pid 

Linux 进程创建

pid_t fork(void); 

  • 通过当前进程创建新进程,当前进程为父进程,新进程为子进程

int execve(const char* pathname, char* const argv[], char* const envp[]);

  • 在当前进程中执行 pathname 指定的程序代码

初识 Linux 进程_第7张图片

fork() 的工作方式

为子进程申请内存空间,并将父进程数据完全复制到子进程空间中

两个进程中的程序执行顺序完全一致 (fork() 函数调用位置)

不同之处:

  • 父进程:fork() 返回子进程 pid
  • 子进程:fork() 返回0
  • 通过 fork() 返回值判断父子进程,执行不同代码

fork() 初探

#include 
#include 
#include 

static int g_global = 0;

int main()
{
    int pid = 0;
    
    printf("Hello World!\n");
    printf("current pid = %d\n", getpid());

    if( (pid = fork()) > 0 )
    {
        g_global = 1;
        
        usleep(100);
        
		printf("child pid = %d\n", pid);
        printf("%d g_global = %d\n", getpid(), g_global);
    }
    else
    {
        g_global = 10;
        
		printf("parent pid = %d\n", getppid());
        printf("%d g_global = %d\n", getpid(), g_global);
    }
    
    return 0;
}



程序运行结果如下图所示

初识 Linux 进程_第8张图片

fork() 前,打印当前进程的 pid 为 6619

fork() 后,当前进程会创建出和自己一样的子进程,随后,父进程和子进程会同时运行,通过 fork() 的返回值可以判断出父进程和子进程,由于父进程 sleep 100us,所以子进程会先打印,然后父进程再打印

思考

如何理解 "每个 Linux 进程可以执行一个或多个程序"?

每个 Linux 进程 可以读取一个或多个程序的代码和数据,从而来执行程序

execve(...) 的工作方式

根据参数路径加载可执行程序

通过可执行程序信息构建进程数据,并写入当前进程

将程序执行位置重置到入口地址处 (即:main())

execve(...) 将重置当前进程空间 (代码 & 数据) 而不会创建新进程

execve 初探

helloworld.c

#include 
#include 
#include 

int main()
{
    printf("exec = %d, %s\n", 
                   getpid(), "Hello World!");
    return 0;
}


test.c

#include 
#include 
#include 

#define EXE "helloworld.out"

int main()
{
    char* args[] = {EXE, NULL};
    
    printf("begin\n");
    
    printf("pid = %d\n", getpid());  
    
    execve(EXE, args, NULL);
    
    printf("end\n");
    
    return 0;
}


我们将 helloworld.c 编译为 helloworld.out,将 test.c 编译为 test.out,来查看运行结果

初识 Linux 进程_第9张图片

execve 不会创建新的进程,而是将当前进程的进程数据替换为 helloworld.out 的进程数据,并执行,所以执行完 helloworld.out后就结束了,不会打印 end 

我们可以 fork 子进程来执行 execve,这样当前进程的进程数据就不会被替换了

#include 
#include 
#include 

#define EXE "helloworld.out"

int create_process(const char* pathname, char* const argv[])
{
	int ret = 0;
	
	if((ret = fork()) == 0)
	{
		execve(pathname, argv, NULL);
	}
	
	return ret;
}

int main()
{
    char* args[] = {EXE, NULL};
    
    printf("begin\n");
    
    printf("pid = %d\n", getpid());  
    
	printf("child pid = %d\n", create_process(EXE, args));
    
    printf("end\n");
    
    return 0;
}


程序执行结果如下图所示

初识 Linux 进程_第10张图片

通过 fork 子进程的方式来执行 execve,这样父进程的数据不会被替换,父进程成功打印出 end

你可能感兴趣的:(linux)