目录
一、进程控制函数一
二、进程控制函数二
启动进程:(exec系列)
创建新进程:
测试代码:
测试结果:
三、进程控制函数三
结束进程:
测试代码:
测试结果:
四、进程控制函数四
改变进程的流程:(相同颜色请配套食用)
测试代码:
测试结果:
五、进程控制函数五
获取进程状态(组id,组识别码,进程id)
测试代码:
测试结果:
六、进程控制函数六
设置进程状态
测试代码:
测试结果:
七:进程控制函数七
一、进程控制函数一
什么是进程?
进程是操作系统调度的最小单位。注意:线程并不是由操作系统调度的,而是由进程自己调度。
进程有多种状态:运行、休眠、结束、暂停、挂起等,进程下的线程也会是相应的状态。
二、进程控制函数二
启动进程:(exec系列)
<unistd.h>
int execl(const char *path, const char *arg, ...)
int execlp(const char *file, const char *arg, ...)
int execle(const char *path, const char *arg, ..., char * const envp[])
int execv(const char *path, char *const argv[])
int execvp(const char *file, char *const argv[])
int execve(const char * filename,char * const argv[ ],char * const envp[ ])内核级别调用
其中:
l :进程执行的参数,以可变参数的形式给出的,这些参数必须以NULL 为最后一个参数
p :exec(进程函数)会将当前的PATH 作为一个参考环境变量。这意味着你填路径时可以不用填绝对路径,可以填相对路径(const char *path)
e :进程函数需要用户来设置这个环境变量(char * const envp[])
v :进程函数会用参数数组来传递argv,数组的最后一个成员必须是NULL(char *const argv[])
这里注意:区别于fork,因为exec系列并不是真正的创建一个,而是用一个用户指定的命令把本进程替换掉。
创建新进程:
pid_t fork( void)
返回值:
大于0 的数,此时就是父进程
等于0 的数,此时就是子进程
小于0 的数,表示调用失败
注意:进程数量是有限的1~32768/32767/65535
测试代码:
void lession32() {
pid_t pid = fork();
if (pid > 0) {//父进程
//sleep(1);
std::cout << "hello,here is parent!\n" << pid << std::endl;
}
else {//子进程
sleep(3);
execl("/usr/bin/ls", "ls", "-l", NULL);//argv的第一个参数一定要是命令自身
}
}
测试结果:
我们可以发现:fork()这个函数有点牛逼,会返回两次,先是父进程再是子进程
注意: 谁是你的父进程谁就调用fork ;fork会返回两次
三、进程控制函数三
结束进程:
以异常方式结束进程: (并不会触发析构等一系列操作)
void abort(void)
若测试的条件不成立则终止进程: (断言的方式终止)(当参数为0时终止进程)
#include <assert.h>
void assert(int expression)
正常结束进程: (可以触发进程结束的调用函数)
void exit(int status)
结束进程执行: (不可以触发)
void _exit(int status)
设置程序正常结束前调用的函数:
int atexit(void (*func)(void))
设置程序正常结束前调用的函数:
int on_exit(void (* function)(int,void*),void *arg)
测试代码:
#include <assert.h>//断言
void lession33_exit() {
printf("%s\n", __FUNCTION__);//打印S函数名称
}
void lession33_on_exit(int status,void*p) {
printf("%s p=%p status=%d \n", __FUNCTION__,p, status);
}
void lession33() {
pid_t pid = fork();
if (pid > 0) {
std::cout << "hello,here is parent!\npid=" << pid << std::endl;
//abort();
atexit(lession33_exit);//进程结束时时调用
exit(0);
}
else {//子进程
sleep(3);
assert(0);//触发断言
on_exit(lession33_on_exit, (void*)1);//子进程结束时时调用
_exit(-1);//不会触发on_exit 的调用
std::cout << "here is son!\r\n";
}
}
测试结果:
四、进程控制函数四
改变进程的流程:(相同颜色请配套食用)
保存目前堆栈环境:
#include <setjmp.h>
int setjmp(jmp_buf environment)
注意:jmp_buf 存储的是寄存器信息(当跳转的时候进行恢复)(补充解释:cpu,无论是arm还是x86架构,大部分cpu的状态存储在寄存器里面。setjmp会把所有buf保存 ,其中的一个buf:IP(指令指针寄存器),在arm叫PC,x86叫IP。所以不同架构下,数据结构不一样。PC:R0,R16 IP:eax,ebx。IP中存储着CPU要执行的指令的值(地址)。)
保存目前堆栈环境:
int sigsetjmp(sigjmp_buf env, int savemask)
这个不仅缓存寄存器,还会缓存上下文
上下文:堆栈、当前寄存器、当前的状态(线程,进程)、下一条指令的位置、栈内存地址
需要再配套struct sigaction食用。具体用法见测试代码
跳转到原先setjmp 保存的堆栈环境:
void longjmp(jmp_buf environment, int value)
改变进程优先顺序:跳转到原先sigsetjmp 保存的堆栈环境
void siglongjmp(sigjmp_buf env, int val)
测试代码实现了一个简单的异常捕获。深入的用法是在逆向中,先setjmp,然后故意触发一些东西,例如崩溃,然后切到中断处理的函数里,进行一些骚操作,然后再恢复。变相实现修改。
测试代码:
#include <setjmp.h>
#include<signal.h>
jmp_buf jmpbuf; //建议设置成全局变量or静态的,不建议设置成局部的,因为后面longjmp会调用,会跨函数
void test003() {
//TODO
longjmp(jmpbuf, 2);
}
void test002() {
//模拟在test002中发生异常
longjmp(jmpbuf, 1); //直接跳转
}
void test001() {
//TODO
test002();
}
void signal_deal(int signo) {
if (signo == SIGSEGV)
longjmp(jmpbuf, SIGSEGV);
}
void lession34() {
signal(SIGSEGV/*断错误(如访问了不该访问的内存)*/, signal_deal); //异常捕获 配合食用
struct sigaction act,actold;
//act.sa_handler = signal_deal;
sigaction(SIGSEGV, &act, &actold);
int ret = setjmp(jmpbuf); //这里setjmp把所有寄存器全部保存了,包括IP寄存器
if ( ret== 0) { //这实际上是,C中处理异常的一种机制
test001();
*(int*)(NULL) = 0;
}
else if (ret == 1) {//错误1的处理和恢复
std::cout << "error 1\n";
}
else if (ret == 2) {//错误2的处理和恢复
std::cout << "error 2\n";
}
else if (ret == SIGSEGV) {//断错误的处理和恢复
std::cout << "error SIGSEGV\n";
}
}
测试结果:
五、进程控制函数五
获取进程状态(组id,组识别码,进程id)
pid_t getpgid(pid_t pid) //取得进程组识别码
pid_t getpgrp(void) //取得当前进程组识别码
pid_t getpid(void) //取得进程识别码
pid_t getppid(void) //取得父进程的进程识别码 谁派生的我
int getpriority(int which,int who) //取得程序进程执行优先权 优先级可以是负数,越小越牛逼
//对于-20的进程,系统优先响应 对于20的进程,系统会先挂起
注意:进程的进程id是进程的唯一标识,进程id是唯一的(取值范围:0-32768 or 65535)
注意:同一个进程,同一时间内,只能被一个进程调试。所以可以通过一个父进程派生一个子进程调试,然后go掉父进程,这样就可以实现反调试。
默认情况下进程id就是组id
测试代码:
#include <sys/resource.h>
void lession35() {
std::cout << "getpgid某进程组id:" << getpgid(getpid()) << std::endl;
std::cout << "getpgrp当前进程组id:" << getpgrp() << std::endl;
std::cout << "getpid当前进程id:" << getpid() << std::endl;
std::cout << "getppid当前进程的父id:" << getppid() << std::endl;
std::cout << "getpriorit当前进程优先级" << getpriority(PRIO_PROCESS,getpid()) << std::endl;
sleep(15);
}
测试结果:
六、进程控制函数六
设置进程状态
注意:必须要有足够的权限(启动的有效用户必须要有足够的权限)
int setpgid(pid_t pid,pid_t pgid) //设置进程组识别码
int setpgrp(void) //设置进程组识别码 把组id设置成进程id 如果设置失败返回-1
int setpriority(int which,int who, int prio) //设置程序进程执行优先权 超出用户权限的没法完成 如果设置失败返回-1
int nice(int inc) //改变进程优先级 超出用户权限的没法完成
注意:无法修改进程的id,因为进程id是唯一的标识,即便root也不可以
测试代码:
void lession36() {
std::cout << "--getpgrp当前进程组id:" << getpgrp() << std::endl;
std::cout<<"setpgid:"<<setpgid(getpid(),1)<<std::endl;
std::cout << "--getpgrp当前进程组id:" << getpgrp() << std::endl;
std::cout<<"setpgrp:"<<setpgrp()<<std::endl;
std::cout << "--getpgrp当前进程组id:" << getpgrp() << std::endl;
std::cout << "--getpriorit:" << getpriority(PRIO_PROCESS, getpid()) << std::endl;
std::cout << "改变优先级nice:" << nice(3)<< std::endl;
std::cout << "--getpriorit:" << getpriority(PRIO_PROCESS, getpid()) << std::endl;
std::cout << "设置优先级setpriorit:" << setpriority(PRIO_PROCESS, getpid(), -1) << std::endl;
std::cout << "--getpriorit:" << getpriority(PRIO_PROCESS, getpid()) << std::endl;
}
测试结果:
七:进程控制函数七
<stdlib.h>
int system(char *command)//执行shell 命令
//例如ls -l;
//可以通过这个来进行组合命令,达到类似于批处理的功能
<sys/types.h>
<sys/wait.h>
int wait(int *status)//等待一个状态(子进程的状态) 一般来讲是和fork 配套使用
//当子进程结束的时候会得到一个返回值 并且状态值会被设置,如果status是空指针那么状态值会被丢弃,如果是有效的地址,那么状态值会设置到地址中
//流程上:先调用fork,再调用wait(父进程调用) fork 开辟一个子进程 在开辟过程中会有一个SIGCHILD的量,意思如果开辟成功则会发送一个信号量过来。那么wait就会等待子进程结束
//因为当子进程销毁时,会向父进程报告(发送SIGCHILD)。如果父进程没有接收到这个报告,则子进程可能被阻塞成为僵尸进程(会占用进程id--pid,会消耗系统资源)
pid_t waitpid(pid_t pid,int * status,int options)//等待指定子进程的中断或结束
options:
WNOHANG //非阻塞,非挂起
WUNTRACED //被调试,主要用于反调试
WCONTINUED //发生了信号导致进程暂停 例如:SIGSTOP(进程停止) SIGPAUSE(进程暂停) SIGCONT(恢复) 注意后两个状态自己不可处理,由系统处理
status:
WIFEXITED(status) //是否退出,一个宏,返回1表示退出,0表示正在运行
WEXITSTATUS(status) //获取status的具体值
WIFSIGNALED(status) //是否有信号量过来导致暂停
WTERMSIG(status) //拿到信号量,是什么一个信号量导致暂停
WIFSTOPPED(status) //导致停止
WSTOPSIG(status) //拿到信号,什么信号量导致停止