您的位置:首页 > 运维架构 > Linux

Linux学习-3进程与线程

2020-08-10 21:42 1256 查看

目录

1 进程在内核中的组织

1.1 进程在内核的组织形式:进程控制块(PCB)(初始化信息)

1.1.1 Linux进程控制块:task_struct结构

1.1.2 task_struct:进程状态

1.1.3 task_struct:文件管理

1.1.4 task_struct:内存管理

1.1.5 进程在内核中的组织形式

2 进程属性

2.1 进程基本属性-进程ID

2.2 真实用户和有效用户的关系

2.3 有效用户和真实用户实例

2.4 普通用户能够修改自己的密码的原因

3 进程生命周期

3.1 进程的启动

3.2 进程的终止

3.3 进程的生命周期

3.3.1 终止进程的函数

3.3.2 exit与return的区别

3.3.3 注册终止处理函数

4 进程环境

4.1 进程内存空间布局

4.1.1 命令行参数

4.1.2 C程序中main函数参数

4.1.3 环境变量表

4.1.4 设置环境变量(putenv、setenv、unsetenv)

5 创建进程

5.1 创建进程

5.2 创建子进程

5.2.1 创建子进程示例

5.2.2 fork函数工作流程

5.2.3 fork函数执行后父子进程的主要异同

5.2.4 父子进程共享文件

5.2.5 fork的用法

5.2.6 vfork函数

6 获知子程序运行状态改变

6.1 僵尸进程

6.2 wait函数

6.3 waitpid函数

7 在进程中运行可执行文件

7.1 exec系列函数

7.2 execl函数

7.3 execv函数

7.4 execle函数

7.5 其他exec函数

 

7.6 exec类函数之间的关系

8 Linux线程控制

8.1 线程的基本概念

8.2 线程与进程的对比

8.3 线程ID

8.4 线程的创建

8.5 线程的终止

8.6 取消线程

8.7 父线程等待子线程终止

8.7.1 pthread_join函数

1 进程在内核中的组织

CPU载入程序到内存--->CPU初始化程序--->CPU进程调度程序

1.1 进程在内核的组织形式:进程控制块(PCB)(初始化信息)

初始化:进程ID,用户ID,进程状态,调度信息,文件管理,虚拟内存管理,信号,时间和定时器...

1.1.1 Linux进程控制块:task_struct结构

1.1.2 task_struct:进程状态

1.1.3 task_struct:文件管理

1.1.4 task_struct:内存管理

1.1.5 进程在内核中的组织形式

2 进程属性

2.1 进程基本属性-进程ID

2.2 真实用户和有效用户的关系

2.3 有效用户和真实用户实例

2.4 普通用户能够修改自己的密码的原因

3 进程生命周期

3.1 进程的启动

◼C程序的启动函数是main,也是进程代码的入口点
  • main ( int argc, char *argv[] );
◼当内核启动C程序时,会在调用main函数前调用特殊的启动函数来获取main函数地址和传递给main函数的参数,并且将这些信息填写到进程控制块中

3.2 进程的终止

正常终止
  • 从main函数中返回
  • 在任意代码中调用exit函数或_exit函数
  • 最后一个线程从其启动例程中返回
  • 最后一个线程调用pthread_exit函数
异常终止
  • 在任意代码中调用abort函数
  • 接收到终止信号

3.3 进程的生命周期

3.3.1 终止进程的函数

◼头文件stdlib.h ,函数定义:void exit( int status ) ◼头文件unistd.h,函数定义:void _exit (int status ) ◼调用这两个函数均会正常地终止一个进程 ◼调用_exit 函数将会立即返回内核 ◼调用exit 函数:
  • 执行预先注册的终止处理函数
  • 执行文件I/O操作的善后工作,使得所有缓冲的输出数据被更新到相应的设备
  • 返回内核

3.3.2 exit与return的区别

  • return是C语言关键字,exit是POSIX API函数
  • 在main函数中,执行return和调用exit函数会产生相同的效果
  • 在子函数中,执行return仅仅从子函数中返回,而调用exit函数将会退出当前进程 

3.3.3 注册终止处理函数

  1. 当进程终止时,程序可能需要进行一些自身的清理工作,如日志登记、资源释放等
  2. 通过atexit函数或on_exit函数允许进程注册若干终止处理函数,当进程终止时,这些终止处理函数将会被自动调用 
   ◼头文件stdlib.h
  • int atexit(void (*func)(void));
  • int on_exit (void (*func)(int, void *), void *arg);
◼ANSI C规定一个进程最多能注册32个终止处理函数 ◼当显示调用或者隐含调用exit函数终止进程(从main中返回、最后一个线程退出等)将会回调这些注册的终止处理函数(最先注册的函数最后被回调) ◼显示调用_exit函数终止进程时将不会回调这些注册的终止函数    示例代码:atexit 示例代码:on_exit  

4 进程环境

4.1 进程内存空间布局

正文:CPU执行的代码部分,正文段通常是共享、只读的

初始化的数据:包含了程序中需明确赋初值的变量,如全局变量int maxcount=99

未初始化的数据:程序执行之前,将此段中的数据初始化为0,如全局变量long sum[1000]

栈/堆:用于动态分配内存

命令行参数和环境变量:主要用于支撑函数调用存放参数、局部变量等

4.1.1 命令行参数

ls [参数] <路径或文件名>
  • ls –l /home
mkdir [参数] <目录名>
  • mkdir -p /home/xiaokun/src
cp [参数] <源文件路径> <目标文件路径>
  • cp –r /usr/local/src /root

4.1.2 C程序中main函数参数

4.1.3 环境变量表

 ◼每个进程都会有自己的环境变量表 ◼通过全局的环境指针(environ)可以直接访问环境变量表(字符串数组)  
  • 头文件unistd.h
  • extern char **environ;
◼环境变量字符串形式为“name=value”,name是环境变量名称,value为环境变量赋值

4.1.4 设置环境变量(putenv、setenv、unsetenv)

设置环境变量的三种方法:
  • putenv
  • setenv
  • unsetenv
putenv函数将环境变量字符串放入环境变量表中;若该字符串已经存在,则覆盖
  • 头文件:stdlib.h
  • int putenv(char *str);
  ◼setenv
  • 头文件:stdlib.h
  • int setenv(const char* name,const char* value, int rewrite);
  • setenv将指定环境变量的值设置为参数指定值(更改环境变量字符串)
  • 若name已经存在 ,rewrite不等于0时,则删除其原先的定义; rewrite等于0,则不删除其原先的定义。
unsetenv
  • 头文件:stdlib.h
  • int unsetenv(const char* name);
  • 删除指定的环境变量字符串

5 创建进程

5.1 创建进程

◼Linux中创建进程的方式:
  • 在shell中执行命令或可执行文件(由shell进程调用fork函数创建子进程)
  • 在代码中(已经存在的进程中)调用fork函数创建子进程 (通过fork函数创建的进程为已经存在进程的子进程)
◼Linux系统中进程0(PID=0)是由内核创建,其他所有进程都是由父进程调用fork函数所创建的 ◼Linux系统中进程0在创建子进程(PID=1,init进程)后,进程0就转为交换进程或空闲进程 ◼进程1(init进程)是系统中其他所有进程的共同祖先

5.2 创建子进程

函数原型
  • 头文件:unistd.h
  • pid_t fork(void);
返回值 ⚫fork函数被正确调用后,将会在子进程中和父进程中分别返回!!
  • 在子进程中返回值为0(不合法的PID,提示当前运行在子进程中)
  • 在父进程中返回值为子进程ID(让父进程掌握所创建子进程的ID号)
⚫出错返回-1

5.2.1 创建子进程示例

5.2.2 fork函数工作流程

◼子进程是父进程的副本
  • 子进程复制/拷贝父进程的PCB、数据空间(数据段、堆和栈)
  • 父子进程共享正文段(只读)
◼子进程和父进程继续执行fork函数调用之后的代码 ◼为了提高效率,fork后不并立即复制父进程数据段、堆和栈,采用了写时复制机制(Copy-On-Write)
  • 当父子进程任意之一要修改数据段、堆、栈时,进行复制操作,并且仅复制修改区域

5.2.3 fork函数执行后父子进程的主要异同

5.2.4 父子进程共享文件

◼父子进程对共享文件的常见处理方式
  • 父进程等待子进程完成。当子进程终止后,文件当前位置已经得到了相应的更新
  • 父子进程各自执行不同的程序段,各自关闭不需要的文件

5.2.5 fork的用法

父进程希望复制自己(共享代码,复制数据空间),但父子进程执行相同代码中的不同分支
  • 网络服务程序中,父进程等待客户端的服务请求,当请求达到时,父进程调用fork创建子进程处理该请求,而父进程继续等待下一个服务请求到达
父子进程执行不同的可执行文件(父子进程具有完全不同的代码段和数据空间)
  • 子进程从fork返回后,立即调用exec类函数执行另外一个可执行文件

5.2.6 vfork函数

◼vfork用于创建新进程,而该新进程的目的是执行另外一个可执行文件 ◼由于新程序将有自己的地址空间,因此vfork函数并不将父进程的地址空间完全复制到子进程中 ◼子进程在调用exec或exit之前,在父进程的地址空间中运行 ◼vfork函数保证子进程先执行,在它调用exec或者exit之后,父进程才会继续被调度执行(父进程处于TASK_UNINTERRUPTIBLE状态)

6 获知子程序运行状态改变

 ◼当一个进程发生特定的状态变化(进程终止、暂停以及恢复)时,内核向其父进程发送SIGCHLD信号 ◼父进程可以选择忽略该信号,也可以对信号进行处理(默认处理方式为忽略该信号) ◼wait或waitpid函数可以用于等待子进程状态信息改变,并获取其状态信息   

6.1 僵尸进程

◼进程在退出之前会释放进程用户空间的所有资源,但PCB等内核空间资源不会被释放
  • 当父进程调用wait或waitpid函数后,内核将根据情况关闭该进程打开的所有文件,释放PCB(释放内核空间资源)
  • 对于已经终止但父进程尚未对其调用wait或waitpid函数的进程( TASK_ZOMBIE状态),称为僵尸进程
◼如果父进程在子进程终止之前终止,则子进程的父进程将变为init进程,保证每个进程都有父进程,由init进程调用wait函数进行善后  

6.2 wait函数

功能:获取任意子进程的状态改变信息(如果是终止状态则对子进程进行善后处理) 函数原型
  • 头文件:sys/wait.h
  • pid_t wait(int *statloc);
参数与返回值
  • statloc:用于获取子进程的状态改变
  • 返回值:若成功返回状态信息改变子进程ID,出错返回-1
参数statloc
  • statloc可以为空指针,此时父进程不需要具体了解子进程的状态变化,只是为了防止子进程成为僵尸进程,或者因为同步原因需等待子进程终止
  • 若statloc不是空指针,则内核将子进程状态改变信息存放在它指向的存储空间中
子进程状态改变信息包含了多种类型的信息,可以通过系统提供的宏来快速解析子进程的状态 调用wait函数之后,父进程可能出现的情况
  • 如果所有子进程都还在运行,则父进程被阻塞(TASK_INTERRUPTIBLE状态),直到有一个子进程终止或暂停,wait函数才返回
  • 如果已经有子进程进入终止或暂停状态,则wait函数会立即返回
  • 若进程没有任何子进程,则立即出错返回-1
如果一个进程有几个子进程,那么只要有一个子进程状态改变,wait函数就返回 如何才能使用wait函数等待某个特定子进程的状态改变?
  • 调用wait,然后将其返回的进程ID和所期望的子进程ID进行比较
  • 如果ID不一致,则保存该ID,并循环调用wait函数,直到等到所期望的子进程ID为止

6.3 waitpid函数

功能:等待某个特定子进程状态改变 函数原型
  • 头文件:sys/wait.h
  • pid_t waitpid(pid_t pid, int *statloc, int options);
返回值
  • 成功返回终止子进程ID,失败返回-1
参数 ⚫pid
  • pid == -1:等待任意子进程执行终止(同wait)
  • pid > 0:等待进程ID为pid的子进程执行终止
  • pid == 0:等待其组ID等于调用进程组ID的任意子进程
  • pid < -1:等待其组ID等于pid绝对值的任意子进程
⚫statloc:存放子进程终止状态 ⚫options:可以为0,也可以是以下常量或常量的或
  • WCONTINUED:如果有暂停的进程由于SIGCONT信号的到来而继续运行,则函数将返回
  • WUNTRACED:如果有处于终止状态的进程,则函数返回
  • WNOHANG:如果没有任何已经终止的子进程则马上返回, 函数不等待,此时返回值为0
waitpid的特有功能  ◼waitpid可等待一个特定的进程的状态改变信息  ◼waitpid可以实现非阻塞的等待操作,有时希望取得子进程的状态改变信息,但不希望阻塞等待子进程状态改变 ◼waitpid支持作业控制(进程组控制 )   

7 在进程中运行可执行文件

7.1 exec系列函数

◼进程调用exec系列函数在进程中加载执行另外一个可执行文件 ◼exec系列函数替换了当前进程(执行该函数的进程)的正文段、数据段、堆和栈(来源于加载的可执行文件) ◼执行exec系列函数后从加载可执行文件的main函数开始重新执行 ◼exec系列函数并不创建新进程,所以在调用exec系列函数后其进程ID并未改变,已经打开的文件描述符不变 ◼execl execle execlp execv execve execvp ◼六个函数开头均为exec,所以称为exec系列函数
  • l:表示list,每个命令行参数都说明为一个单独的参数
  • v:表示vector,命令行参数放在数组中
  • e:表示由函数调用者提供环境变量表
  • p:表示通过环境变量PATH来指定路径,查找可执行文件

7.2 execl函数

函数原型
  • 头文件:unistd.h
  • int execl(const char *pathname,const char *arg0, ...,NULL);
参数
  • pathname:要执行程序的绝对路径名
  • 可变参数:要执行程序的命令行参数,以空指针结束
返回值
  • 出错返回-1
  • 成功该函数不返回!

7.3 execv函数

函数原型
  • 头文件:unistd.h
  • int execv(const char *pathname, char *const argv[]);
参数
  • pathname:要执行程序的绝对路径名
  • argv:数组指针维护的程序命令行参数列表,该数组的最后一个成员必须为空指针
返回值
  • 出错返回-1
  • 成功该函数不返回

7.4 execle函数

函数原型
  • 头文件:unistd.h
  • int execle(const char *pathname, const char *arg0,... NULL, char *const envp[]);
参数
  • pathname:要执行程序的绝对路径名
  • 可变参数:要执行程序的命令行参数,以空指针结束
  • envp指向环境字符串指针数组的指针,该数组的最后一个成员必须为空指针
返回值
  • 出错返回-1
  • 成功该函数不返回

7.5 其他exec函数

 

execve函数
  • int execve(const char *pathname,char *const argv[], char *const envp[]);
execlp函数
  • int execlp(const char *filename,const char *arg0, ...,NULL);
  • filename参数可以是相对路径(路径信息从环境变量PATH中获取)
  • 例如默认环境变量中包含的PATH=/bin:/usr/bin:/usr/local/bin/
execvp函数
  • int execvp(const char *filename,char *const argv[]);

7.6 exec类函数之间的关系

8 Linux线程控制

8.1 线程的基本概念

 ◼ 进程的概念体现出两个特点:资源(代码和数据空间、打开的文件等)以及调度/执行。线程是进程内的独立执行代码的实体和调度单元 ◼ 一个进程内的所有线程共享进程的很多资源(这种共享又带来了同步问题)   

8.2 线程与进程的对比

线程只拥有少量在运行中必不可少的资源
  • PC指针:标识当前线程执行的位置
  • 寄存器:当前线程执行的上下文环境
  • 栈:用于实现函数调用、局部变量(局部变量是私有的)
进程占用资源多,线程占用资源少,使用灵活 线程不能脱离进程而存在,线程的层次关系,执行顺序并不明显,会增加程序的复杂度 没有通过代码显示创建线程的进程,可以看成是只有一个线程的进程  

8.3 线程ID

 ◼同进程一样,每个线程也有一个线程ID ◼进程ID在整个系统中是唯一的,线程ID只在它所属的进程环境中唯一 ◼线程ID的类型是pthread_t,在Linux中的定义:  
  • typedef unsigned long int pthread_t( /usr/include/bits/pthreadtypes.h)
pthread_self函数可以让调用线程获取自己的线ID 函数原型
  • 头文件:pthread.h
  • pthread_t pthread_self();
返回调用线程的线程ID

8.4 线程的创建

pthread_create函数用于创建一个线程 函数原型
  • 头文件:pthread.h
  • int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
调用pthread_create函数的线程是所创建线程的父线程 参数
  • tidp:指向线程ID的指针,当函数成功返回时将存储所创建的子线程ID
  • attr:用于指定线程属性(一般直接传入空指针NULL,采用默认线程属性)
  • start_rtn:线程的启动例程函数指针,创建的线程首先执行该函数代码(可以调用其他函数)
  • arg:向线程的启动例程函数传递信息的参数
返回值
  • 成功返回0,出错时返回各种错误码

8.5 线程的终止

线程的三种终止方式
  • 线程从启动例程函数中返回,函数返回值作为线程的退出码
  • 线程被同一进程中的其他线程取消
  • 线程在任意函数中调用pthread_exit函数终止执行
  线程终止函数 ◼函数原型
  • 头文件:pthread.h
  • void pthread_exit(void *rval_ptr);
◼参数
  • rval_ptr:该指针将参数传递给pthread_join函数(与exit函数参数用法类似)

8.6 取消线程

线程调用该函数可以取消同一进程中的其他线程(即让该线程终止) 函数原型
  • 头文件: pthread.h
  • int pthread_cancel(pthread_t tid);
参数与返回值
  • tid:需要取消的线程ID
  • 成功返回0,出错返回错误编号
◼在默认情况下,pthread_cancel函数与被取消线程(ID等于tid的线程)自身调用pthread_exit函数(参数为PTHREAD_CANCELED)效果等同 ◼线程可以选择忽略取消方式或者控制取消方式 ◼pthread_cancel并不等待线程终止,它仅仅是提出请求

8.7 父线程等待子线程终止

函数原型
  • 头文件:pthread.h
  • int pthread_join(pthread_t thread,void **rval_ptr);
调用该函数的父线程将一直被阻塞,直到指定的子线程终止 返回值
  • 成功返回0,否则返回错误编号

8.7.1 pthread_join函数

参数
  • thread:需要等待的子线程ID
  • rval_ptr:(若不关心线程返回值,可直接将该参数设置为空指针)
            1.若线程从启动例程返回,rval_ptr将包含返回码             2.若线程被取消,rval_ptr指向的内存单元值置为PTHREAD_CANCELED             3.若线程通过调用pthread_exit函数终止,rval_ptr就是调用pthread_exit时传入的参数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: