显示进程(pid,user,all)ps aux
显示进程(pid,ppid,进程组,会话)ps ajx
查看信号kill -l
九号信号的作用kill -9(SIGKILL) pid
每个进程地址空间都是从0开始(虚拟地址空间)
创建进程
函数fork()
*问题:fork之后,进程间的数据共享是怎么样的
答:fork之后进程之间的数据完全一样但是完全独立,互不影响
*问题:数据在物理内存中(需要对数据操作的时候)时,存储几份
答:读时一份,写时两份(读时共享,写时复制)
写的时候,无论父子,都复制一个数据去自己操作
*问题:父子进程之间可以通过全局变量通信吗
答:不可以,因为相互之间数据是独立的(在函数fork的操作下)
exec函数族
让父子进程执行不相干的操作
能够替换进程地址空间中的.text
源代码段
需要指定可执行程序"例如:exec(“a.out”)
实现了换核不换壳的作用
执行另一个程序不许要创建另外的地址空间
应用场景:在一个运行的程序a里调用另外的应用程序b
exec之前需要fork
返回值:成功不返回,失败就需要打印错误信息perror
,退出子进程
perror(s):用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串
函数execl
一般执行自定义的程序
int execl(const char *path, const char *arg, ...)
第一个参数写路径,可以是绝对路径(最好),可以是相对路径
第二个参数写占位,可以写程序名
后面参数可以写函数执行需要的参数
最后的参数一定是NULL
函数execlp
int execlp(const char *file, const char *arg, ...)
执行path环境变量能够搜索到的程序
参数基本上同上,其中file直接写程序名
孤儿进程、僵尸进程、进程回收
孤儿进程
父进程先结束,子进程还存活
孤儿被init领养,init进程变为孤儿进程的父进程
目的:释放系统资源
进程结束之后可以释放用户区空间,但释放不了pcb(进程控制块)
pcb必须由父进程释放
僵尸进程
子进程死了,父进程还存活
此时需要父进程释放pcb,但是父进程没有进行释放
此时,子进程即为僵尸进程
僵尸进程不可以被kill杀死
当父进程死后,僵尸进程会被init进程领养,然后释放
进程回收
函数wait()
wait函数是一个阻塞函数,等待条件是子进程的死亡
pid_t wait(int *status)
返回值 :
- - 1:回收失败,已经没有子进程了
- > 0:回收的子进程对应的pid
参数:status 判断子进程如何死的
正常退出/被某信号杀死
调用一次,只能回收一个子进程
子进程退出状态:
WIFEXITED(status)
:函数返回非零,为正常结束WEXITSTATUS(status)
:如果上宏为真,获取进程退出状态参数WIFSIGNALED(status)
:非零,正常退出WTERMSIG(status)
:如果上宏为真,则获取终止进程的信号
函数wait_pid()
pid_t waitpid(pid_t pid, int *status, int options)
参数:
1.pid
-
pid == -1:回收任一子进程,循环方法
(wpid = waitpid(-1, &status, WNOHANG) != -1)
-
pid > 0:回收其进程ID与PID相等的子进程
-
pid == 0:回收其组ID与调用进程的组ID一致的任一子进程
-
pid < -1:回收其组ID等于PID绝对值的任一子进程
2.status -
子进程的退出状态,用法和wait一致
3.options:设置为WNOHANG,函数非阻塞,设置为0,函数阻塞
返回值:
-
0 返回回收的子进程的PID
- = -1 无子进程
- = 0 子进程正在运行
进程通信(IPC)
管道 - 简单
信号 - 开销小
共享映射区 - 有无血缘关系都可以
本地套接字 - 稳定
pipe(匿名管道)
- pipe只能在有血缘关系的进程间通信
- 不占用磁盘空间,只是一个内存缓冲区,是伪文件
特点: - 两部分:读端和写端,对应两个文件描述符
- 数据写端流入,读端流出
- 操作管道的进程被销毁之后,管道被自动释放
- 管道默认读写都阻塞
原理: - 内部实现方式:环形队列(先进先出)
- 缓冲区大小默认4k
- 大小会根据实际情况适当调整
局限性: - 队列:数据只能读取一次
- 采用半双工的方式实现
- 匿名管道只能在有血缘关系的进程中
创建匿名管道int pipe(int fd[2])
fd[0]读端 fd[1]写端
返回值:
- 0:成功
- -1:失败
- 父子进程间通信是否需要sleep
答:不需要,因为管道的读写会互相阻塞
文件描述符重定向:dup2(oldfd, newfd);
对管道操作时,单个管道应当只在一个进程内读,在另一个进程写,多余的应当关闭
读操作:
- 有数据:read(fd),正常读,返回读出的字节数
- 无数据:
- 写端全部关闭,返回0,相当于读文件读到了尾部
- 没有全部关闭,阻塞
写操作:
- 读端全部关闭,管道破裂,进程终止
- 内核给当前进程发信号SIGPIPE(13)
- 没有被全部关闭
- 缓冲区写满了
- write阻塞
- 缓冲区没满,继续写
- 缓冲区写满了
- 如何设置非阻塞
- 设置读端非阻塞
- fcntl - 变参函数
- 复制文件描述符 - dup
- 修改文件属性 - open的时候对应的flag属性
- 设置方法:
- 获取原来的flag
- fcntl - 变参函数
int flags = fcntl(fd[0], F_GETFL);
flags |= O_NONBLOCK;
fcntl(fd[0], F_SETFL, flags);//设置flag
fifo(有名管道)
特点:
- 磁盘上有文件,属性为p,查看时大小永远为零
- 伪文件,数据存在内核里对应的缓冲区
- 半双工
使用场景: - 无血缘关系的进程间通信
创建方式: - 命令:mkfifo 管道名
- 函数:mkfifo(文件名,模式)
mmap(内存映射)
可以有血缘,也可以没血缘
匿名
mmap-创建内存映射区(动态库加载区)
可以用来修改磁盘文件,也可以文件间通信
函数原型:
void *mmap()
(
void *adrr,//映射区首地址,传NULL
size_t length, //映射区大小 实际上是4k的整数倍 不能为零一般按文件设定
int prot,//映射区权限
PROT_READ
PROT_WRITE
PROT_READ | PROT_WRITE
int flags,//标志位参数
MAP_SHARED//会同步到磁盘
MAP_PRIVATE//不会同步到磁盘
int fd,//文件描述符 要映射的文件对应的fd
//如何得到 open
off_t offset//映射文件偏移量
//映射的时候文件指针的偏移量,必须是4k的整数倍
//一般指定0
)
返回值:
- 调用成功,映射区首地址
- 调用失败,返回MAP_FAILED((viod *) -1)
调用完之后需要释放
munmap — 释放内存映射区
int munmap(void *addr, size_t length);
//addr mmap的返回值,映射区的首地址
//length mmap的第二个参数
- 对mmap的ptr做++等操作后,可以释放吗
答:不能,会变成无效参数,如果想操作,要创建新指针
- 如果open的时候指定的是只读,mmap的时候权限是读写,会怎么样
答:在mmap的时候会出错,权限不够;open指定的权限应该大于等于mmap指定的权限
- 如果文件偏移量指定为1000,会怎么样
答:会mmap失败,必须为4096的整数倍
- 如果不检测mmap的返回值会怎样
答:无法对其进行操作
- mmap什么情况下会调用失败
答:第二个参数为0,第三个参数必须有read,fd权限问题,偏移量必须为4k整数倍
- 可以open的时候O_CREAT一个新文件来创建映射区吗
答:可以,需要做文件拓展,lseek,truncate(path, length),ftruncate(fd, length)
- mmap后关闭文件描述符,对映射区有没有影响
答:没有影响
- 对ptr越界操作会怎样
答:段错误,如果未被占用,就不报错,越界操作的内容也无法同步
- 父子进程间永远共享的东西
答:文件描述符,内存映射区
创建匿名映射区
不需要指定文件描述符,需要在flags的位置传MAP_ANON
无血缘关系的进程通信–通过文件(无法使用匿名)
信号
特点:
- 简单
- 携带的信息量少
- 使用在特定场景中
信号的状态:
- 产生
- 未决状态–没有被处理的状态
- 递达–信号被处理
产生:
- 键盘:ctrl + c
- 命令:kill
- 系统函数kill
- 软条件:定时器
- 硬条件:段错误,除0错误
递达: - 忽略
- 被捕捉,执行自定义操作
- 执行了默认动作
未决: - 被拦截
信号的优先级比较高,一般先处理信号
阻塞信号集,未决信号集:
不能直接操作
储存在pcb里 - 阻塞信号集:放的是要屏蔽的信号
未决信号集: - 没有被处理的信号的集合
相关函数
kill–给指定进程发信号
- 函数原型:
int kill(pid_t pid, int sig)
- pid > 0:发送给指定进程
- pid = 0:给调用kill同一进程组的进程(所有)
- pid < -1:取|pid|发给指定进程组(如何指定??)
- pid = -1:发送给进程有权限发送的所有进程
raise–自己给自己发信号
- 函数原型:
int raise(int sig)
abort–给自己发送异常终止信号SIGABRT
- 函数原型
void abort(void)
闹钟(定时器)
每个进程只有一个定时器
- 函数原型
unsigned int alarm(unsigned int seconds)
- 计时完成发送SIGALARM信号
- 参数为零,就是取消
- 返回值:上一个定时器还剩几秒,没有就为0
- 使用的是自然定时法,不受当前进程影响
- 练习:看看1s计多少数
- real = 用户 + 内核 + 损耗
- 损耗主要在于文件I/O操作
setitimer–定时器,并实现周期性定时
- 函数原型
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)
信号集
类型:sigset_t
- sigemptyset:将set集设空
- sigfillset:将所有信号加入set集合
- sigaddset:添加信号
- sigdelset:删除信号
- sigismember:判断信号是否存在
- sigprocmask:将自定义信号集设置到内核中
- sigpenging:读取当前未决信号集
信号捕捉
signal函数
- 函数原型:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
sigaction
- 函数原型:
int sigaction(int signum,
const struct sigaction *act,
struct sigaction *oldact)
守护进程
特点:
- 后台服务进程
- 独立于控制终端
- 周期性的执行某个任务
- 等待某事件发生
- 不受用户注销登陆的影响
- 一般采用d结尾的名字(服务)
进程组的id就是组长的pid
会话
- 不能是组长创建
- 创建了一个会话就相当于创建了一个守护进程
创建会话:pid_t setsid(void)
获取当前会话:pid_t getsid(pid_t pid)
正规流程:
- 创建子进程,父进程退出(必须)
- 子进程创建会话(必须)
- 改变当前工作目录(非必须)//增加健壮性chdir(“path”)
- 重设文件掩码,因为子进程会继承父进程的掩码,设置为0umask(0)(非必须)//增加灵活性
- 关闭文件描述符0, 1, 2标准输入输出错误流close(0/1/2)释放资源(非必须)
- 执行核心工作(必须)