多用户:多个用户同一时间使用计算机
多任务:同时执行几个任务,并且可以在还没有执行完一个任务的时候执行另一个任务
程序:静态,保存在硬盘上的可执行代码
进程:动态,运行中的程序,表示过程(操作系统资源管理的最小单位)
线程:在进程内部,比进程更小的能独立运行的基本单位
PS.与同属一个进程的其他线程共享进程拥有的全部资源
一个线程可以创建和撤销另一个线程,同进程的多个线程可以并行执行(注意并行与并发的区别)
进程标识:ID(唯一的为非负数)
实际用户:标识运行该进程的用户
有效用户:标识来运行该进程的用户身份
父进程和子进程:相当于父亲和儿子
可以使用下面这几个函数:
pid_t getgid(id) //进程id
pid_t getppid(id) //父进程id
pid_t getuid(id) //实际用户id
pid_t geteuid(id) //有效用户id
pid_t getgid(id) //实际组id
pid_t getegid(id) //有效组id
代码段:二进制机器代码
数据段:存储已被初始化的变量,包括全局变量和已被初始化的静态变量
未初始化数据段:存储未被初始化的静态变量,又称BBS
堆:用于存放程序运行中动态分配的变量
栈:用于函数调用,保存函数的返回地址,函数的参数,函数内部定义的局部变量
PS.高地址还存储了命令行参数和环境变量
运行状态、可中断等待状态、不可中断等待状态、僵死状态、停止状态
PS.用ps命令查看进程的当前状态
< 高优先级进程、N 低优先级进程、L 内存锁页、
s 会话首进程、l 多线程进程、+ 前台进程组
是指内核在内存中如何存放可执行程序文件.在将程序转化为进程的过程中,操作系统将可执行程序由硬盘复制到内存中
①由操作系统创建:进程之间平等,不存在资源继承
PS.在系统启动时,操作系统会创建一些进程,承担着管理和分配系统资源的任务,通常被称为系统进程
②由父进程创建:子进程,继承父进程
#include
#include
pid_t fork(void);
这个函数可以说是很特殊了,它有两个返回值,调用一次返回两次。其实就相当于fork一个进程之后,当前进程分裂为两个,一个父进程,一个刚刚fork的子进程;一个返回值是父进程调用fork函数后的返回值,即刚刚创建的子进程的ID,另一个是子进程中fork函数的返回值,该返回值是0
若进程创建失败,则只返回一个-1,失败原因通常是父进程拥有的子进程的个数超过了规定的限制(超生吗哈哈哈..),此时errno为EAGAIN;可供使用的内存不足也会导致进程创建失败(没钱养孩子惹…),此时errno为ENOMEN
所以鸭鸭可以通过返回值来区别父进程和子进程:父进程返回子进程ID,子进程返回0
vfork函数:
为什么会有vfork呢?
因为以前的fork当它创建一个子进程时,将会创建一个新的地址空间,并且拷贝父进程的资源,然后将会有两种行为:
① 执行从父进程那里拷贝过来的代码段
② 调用一个exec执行一个新的代码段
当进程调用exec函数时,一个新程序替换了当前进程的正文,数据,堆和栈段。这样,前面的拷贝工作就是白费力气了,这种情况下,聪明的人就想出了vfork。vfork并不复制父进程的进程环境,子进程在父进程的地址空间中运行,所以子进程不能进行写操作,并且在儿子“霸占”着老子的房子时候,要委屈老子一下了,让他在外面歇着(阻塞等待),一旦儿子执行了exec或者exit后,相当于儿子买了自己的房子了,这时候就相当于分家了。因此,如果创建子进程是为了调用exec执行一个新的程序的时候,就应该使用vfork
fork与vfork的区别:
① vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的某个行为,则会导致死锁。
② fork要拷贝父进程的进程环境;哪个进程先运行取决于系统的调度算法;而vfork则不需要完全拷贝父进程的进程环境,在子进程没有调用exec和exit之前,子进程与父进程共享进程环境,相当于线程的概念,此时父进程阻塞等待。
什么是孤儿进程呢?
如果一个子进程的父进程先于子进程结束,子进程就成为一个孤儿,它有init进程收养,成为其子进程
什么是守护进程呢?
指在后台运行的,没有控制终端与之相连的进程。它独立于控制终端,通常周期性地执行某种任务。Linux的大多数服务器都是使用守护进程的方式实现。
如何创建一个守护进程?
首先我们需要理解一些基本概念:
进程组(process group): 一个或多个进程的集合,每个进程都有一个进程组ID,这个ID就是进程组长的进程ID
会话期(session): 一个或多个进程组的集合,每个会话有唯一一个会话首进程(session leader),会话ID为会话首进程ID
控制终端(controlling terminal) :每一个会话可以有一个单独的控制终端,与控制终端连接的会话首进程就是控制进程(controlling process)。 这时候,与当前终端交互的就是前台进程组,其他的都是后台进程组。
需要以下操作:
需要两次fork的原因:
1. 第一次fork的作用是让shell认为本条命令已经终止,不用挂在终端输入上。还有一个作用是为后面setsid服务,setsid的调用者不能是进程组组长(group leader),此时父进程是进程组组长。
2. fork第二次主要目的是防止进程再次打开一个控制终端。因为打开一个控制终端的前提条件是该进程必须是会话组长。再fork一次,子进程ID != sid(sid是进程父进程的sid),所以也无法打开新的控制终端。
最终都会执行内核中的同一段代码,用来关闭进程已打开的文件描述符,释放它所占用的内存和其他资源
比较:
1. exit和return的区别:exit是一个有参数的函数,而return是函数执行完后的返回.exit把控制权交给系统,而return交给调用函数
2. exit和about:exit是正常终止进程,而about是异常终止
3. exit(0)正常终止,exit(1)异常终止
4. exit()和_exit()的区别:exit在头文件stdlib.h中声明,而_exit()声明在unistd.h中,两个函数均能正常终止进程,但是_exit()会执行后立即返回给内核,而exit()要先执行一些清楚操作,然后把控制权交给内核
什么是僵尸进程?
当子进程先于父进程终止,而父进程又没有调用wait函数等待子进程结束,子进程进入僵死状态,并且会一直保持下去除非系统重启.子进程处于僵死状态,内核只保存该进程的一些必要信息以备父进程所需.此时子进程始终占用着资源,同时也减少了系统可以创建的最大进程数;如果子进程先于父进程终止,且父进程调用了wait或waitpid函数,则父进程会等待子进程结束
使用fork或vfork创建子进程后,子进程通常会调用exec函数来执行另一个程序.系统调用exec用于执行一个可执行程序以代替当前进程的执行映像;一个进程一旦调用exec函数,它本身就死亡了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,惟一保留的就是进程ID,对系统而言还是一个进程,不过执行的已经是另一个程序了
exexc函数族:
#include
int execve(const char *path,char * const argv[],char *const envp[]);
int execv(const char *path,char *const envp[]);
int execle(const char *path,const char *arg,...);
int execl(const char *path,const char *arg,...);
int execvp(const char *file,char* const argv[]);
int execlp(const char *file,const char *arg,...);
exec函数错误表:
errno | 错误描述 |
---|---|
EACCES | 指向的文件或脚本文件没有设置可执行位,即指定的文件是不可执行的 |
E2BIG | 新程序的命令行参数与环境变量容量之和超过ARG_MAX |
ENOEXEC | 由于没有正确的格式,指定的文件无法执行 |
ENOMEN | 没有足够的内存空间来执行指定的程序 |
ETXTBUSY | 指定文件被一个或多个进程以可写的方式打开 |
EIO | 从文件系统读入文件时发生I/O错误 |
执行新程序后的进程除了保存原来的进程ID,父进程ID,实际用户ID,实际组ID之外,进程还保持了许多原有特征,主要有:
1. 当前工作目录
2. 根目录
3. 创建文件时使用的屏蔽字
4. 进程信号屏蔽字
5. 未决警告
6. 和进程相关的使用处理器的时间
7. 控制终端
8. 文件锁
当子进程先于父进程退出时,如果父进程没有调用wait和waitpid函数,子进程就会进入僵死状态,如果父进程调用了waitpid函数,就不会使子进程变为僵尸进程
#include
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);
wait函数使父进程暂停执行,直到它的一个子进程结束为止,该函数的返回值是终止运行的子进程PID,参数statloc所指向的变量存放子进程的退出码,即从子进程的main函数返回的值或子进程中exit函数的参数;如果statloc不是一个空指针,状态信息将被写入它指向的变量
waitpid也用来等待子进程的结束,但它用于等待某个特定进程结束,参数pid指明要等待的子进程PID.statloc的含义与wait函数中相同,options允许用户改变waitpid的行为,若将该参数赋值为WNOHANG,则使父进程不被挂起而立即返回并执行其后的代码
#include
#include
int setuid(uid_t uid); //设置实际用户有效用户ID
int setgid(gid_t gid); //设置实际组有效组用户ID
只有root用户才能更改实际用户ID,所以一个非特权用户进程是不能通过setuid或setgid得到root用户权限的.
但是su命令为什么可以把一个普通用户变成root用户呢?
因为su是一个set_uid程序,执行了一个设置了set_uid位的程序时,内核将进程的有效用户ID设置为文件属猪(啊呸!属主……)的ID.而内核检查一个进程是否具有访问某文件的权限时,是使用进程的有效用户ID进行检查的,su程序的文件属主是root,普通用户运行su命令时,su进程的权限是root用户
#include
int nice(int increment);
nice函数可以改变进程的优先级
#include
int getpriority(int which,int who);
int setpriority(int which,int who,int prio);
getpriority函数返回一组进程的优先级
which | who | |
---|---|---|
PRIO_PROCESS | 一个特定的进程 | 进程ID |
PRIO_PGRP | 一个进程组的所有进程 | 进程组ID |
PRIO_USER | 一个用户拥有的所有进程 | 实际用户ID |
如果getpriority函数调用成功,返回指定进程的优先级,如果出错返回-1,并设置errno的值,errno可能取值如下:
ESRCH:which和who的组合与现存的所有进程均不匹配
EINVAL:which是个无效的值
当指定的一组进程的优先级不同时,getpriority将返回其中优先级最低的一个
setpriority函数用来设置指定进程的优先级