进程与线程
进程
进程基本概念
-
进程:操作系统对程序的加载并运行的动态概念
-
进程在内核中的组织形式:进程控制块PCB
-
进程控制块的组织结构
- 物理组织结构:进程
pcb
调度队列 - 逻辑组织结构:进程创建过程中形成的前后逻辑树关系
- 物理组织结构:进程
-
进程状态以及状态切换
TASK_RUNNING
TASK_INTERRUPTIBLE
TASK_UNINTERTUPTIBLE
TASK_SOTPPED
TASK_ZOMBILE
-
进程的文件管理:
task_struct
索引机制 -
进程的内存管理:
task_struct
索引机制 -
进程的主要特点:
- 调度和执行:进程状态转换
- 资源
- 内核空间:PCB进程控制块
- 用户空间:正文段、数据段、堆栈段和打开的文件等
进程基本属性
-
ps -aux
: process status 查看系统正在运行的所有进程的详细属性 -
进程ID:获取进程ID
getpid(void)
-
进程真实用户和真实用户组:获取进程真实用户ID
getuid(void)
,获取进程真实用户组IDgetgid(void)
-
进程有效用户和有效用户组:获取进程有效用户ID
geteuid(void)
,获取进程有效用户组IDgetegid(void)
-
有效用户与真实用户实例
#include <stdio.h> #include <stdlib.h> #incldue <unistd.h> #include <sys/types.h> int main(int argc, char const *argv[]) { printf("real uid: %d,real gid: %d \n",getuid(),getgid()); printf("effective uid: %d, effective gid: %d\n",geteuid(),getegid()); exit(0); } /* ./3.1.1 //查看真实用户和有效用户的ID ls -l 3.1.1 //查看是否设置了用户ID chmod u+s 3.1.1 //添加文件特殊属性用户ID位 su //切换到root权限 chown root 3.1.1//改变文件的所有者 ./3.1.1 //查看有效用户ID是否发生变化 有效用户ID会变成其所有者的用户ID */
-
用户密码
/etc/password
:存储所有用户信息etc/shadow
:存储用户密码password
:修改用户密码命令,其有效用户ID为root- LINUX根据进程的的有效用户进行权限检查
进程生命周期
-
进程的启动:进程代码的入口
main(int argc,char const *argv[])
,内核启动C程序是,在调用main函数之前调用特殊的启动函数获取main函数地址和传递给它的参数,并将这些信息写入pcb
-
进程的终止
- 正常终止,从main函数返回;调用exit()或者_exit()终止;最后一个线程从其启动例程中返回;最后一个线程调用
pthread_exit()
终止 - 异常终止,调用
abort()
终止
- 正常终止,从main函数返回;调用exit()或者_exit()终止;最后一个线程从其启动例程中返回;最后一个线程调用
-
进程的生命周期
- 调用:C启动例程 >> main() >> 用户函数 返回:用户函数 >> main() >> C例程
- 调用exit() >> 终止处理函数 >> 标准I/O清理函数 >> 调用_exit()返回到kernel
-
终止进程的函数
-
void exit(int status)
:执行完终止处理函数和标准I/O处理函数之后才会返回内核 -
void _exit(int status)
:终止当前进程的生命周期,并立即返回内核 -
exit
与return
的区别:- return是关键字,exit()是
API
- 在main函数中两者产生相同的效果
- return退出的是函数,从子函数中返回;而exit()则会终止进程
- return是关键字,exit()是
-
终止处理函数:日志登记、资源释放等善后工作,通过
atexit()/on_exit()
注册若干个终止处理函数
-
创建进程
-
进程0:内核创建的进程,进程0创建
init
进程(进程1)后转为交换进程或者空闲进程 -
进程1:
init
进程是系统中所有其他进程的祖先pstree
-
创建子进程
fork()
:在子进程中返回值为0提示正在子进程中,在父进程中返回值为子进程pid
- 子进程拷贝父进程的PCB和数据空间(数据段和堆栈)
- 子进程和父进程共享正文段
- 子进程和父进程都会执行调用
fork()
之后的代码
int main(int argc, char const *argv[]) { pid_t pid; pid = fork(); if(pid == -1) printf("fork error\n"); else if(pid == 0){ printf("the returned value is %d \n",pid); printf("in child process\n"); printf("My pid is %d\n",getpid()); }else{ printf("the returned value is %d \n",pid); printf("in father process\n"); printf("My pid is %d\n",getpid()); } return 0; }
-
创建进程的操作:
- 为进程分配内核空间即进程控制块PCB
- 为进程分配用户空间报告正文段、数据段、堆(动态分配的空间)、栈(函数调用)
-
父子进程的异同
相同
- 真实和有效用户ID相同
- 环境变量相同
- 数据空间相同
- 打开的文件相同(父子进程文件共享)
不同
- fork()函数的返回值不同
- 进程ID不同
- 子进程的时间时钟数据会被清零
-
创建独立进程
vfork()
:fork
创建新进程将拥有自己的地址空间,但是在调用exec
或exit
离开父进程之前是在父进程的地址空间中运行,并在此期间子进程优先执行而父进程处于阻塞状态 -
exec
系列函数:进程调用exec
系列函数后,进程会变成执行该函数的进程,exec
程序将改变进程的用户空间内容包括正文段、数据段和堆栈,并从加载的可执行文件的main函数开始重新执行。调用exec
系列函数不改变进程的PCB所以不产生新的进程。-
exec系列函数作用是提供可执行文件的命令行参数或者环境变量
-
execl
:int execl(const char *pathname, const char *argv0,...,NULL);
-
int main(int argc,char const *argv[]) { printf("entering main process \n"); if(fork()==0) { execl("/bin/ls","ls","-l",NULL);//参数个数根据NULL结尾获得 printf("exiting main process\n");//execl可执行文件的代码会覆盖父进程的正文段,execl以后的代码都不会执行且不会有返回值 } return 0; }
-
execle
:int execle(const char *pathname, const char *argv0,...,NULL,char *envp[]);
-
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { char *envp[] = {"PATH=/tmp","USER=shan",NULL};//环境变量字符串 if(fork()==0) { if(execle("/bin/ls","ls","-l",NULL,evnp)<0) perror("execle error\n"); } return 0; }
-
execlp
:int execle(const char *pathname, const char *argv0,...,NULL,char *envp[]);
-
execv
:int execv(const char *pathname,char *const argv[]);
-
int main(int argc,char const *argv[]) { int ret; char *argv[] = {"ls","-l",NULL}; printf("entering main process \n"); if(fork()==0) { ret = execv("/bin/ls",argv); if(ret == -1) perror("execv error"); printf("exiting main process\n"); } return 0; }
-
execve
:int execve(const char *pathname, char *const argv[],char *envp[]);
-
execvp
:int execvp(const char *pathname,char *const argv[]);
-
l
: list 提供一系列命令行参数 -
v
:vector 命令行参数放在一个数组中 -
e
:由函数调用者提供环境变量表 -
p
:path 指定环境变量路径,查找可执行文件
-
线程
线程的基本概念
-
线程:进程内的独立执行代码的实体和调度单元
-
一个进程内的多个线程的关系:
- 共享:共享打开的文件、全局变量等资源
- 私有:线程ID、存放局部变量的栈、寄存器集合(PC指针和上下文环境)等
-
没有通过代码显示创建线程的进程看作只有一个线程的进程
-
线程ID:
pthread_t
仅仅在进程环境中是唯一的
线程和进程的操作
操作 | 进程操作API | 线程操作API |
---|---|---|
线程创建 | fork(),vfork() | pthread_create() |
终止 | exit() | pthread_exit(),pthread_cancel() |
等待 | wait(),waitpid() | pthread_join() |
获取ID | getpid() | pthread_self() |
-
创建子线程实例
void *childthread(void) { int i; for(i=0;i<10;i++) { printf("childthread message\n"); sleep(100); } } int main(int argc,char const *argv[]) { pthread_t tid; printf("create childthread\n"); pthread_create(&tid,NULL,(void*)childthread,NULL); pthread_join(tid,NULL); printf("childthread exit\n"); return 0; }