ps 可以查看进程,默认情况下只能当前终端启动的进程。
ps -ef 可以查看所有进程(Unix/Linux)
ps -aux 也可以
ps -aux是Linux特有的命令,Unix不直接支持。/usr/ucb/ps 支持。
Unix命令:
| more 可以分页显示结果,回车滚动一行,空格翻过一页,q退出。
ps -aux | more
whereis 可以查看什么东西在哪里
主流的操作系统都支持多进程并行(同时运行),但多进程启动是有次序的。比如:Linux由进程0 启动进程1和进程2(有些系统进程2是由进程1启动),其他进程都由这两个进程直接/间接启动。
如果进程a启动了进程b,a叫父进程,b叫子进程。
进程的常见状态:
S 进程处于休眠状态,多数进程都是休眠
R 运行状态,正在运行
O 准备运行的状态
Z 僵尸进程
僵尸进程就是进程已经结束,但资源没有回收。
系统管理进程的方式:
每个进程都有自己的编号,叫进程ID(PID),函数getpid()可以获取自己进程的ID。函数getppid()可以获取父进程的PID。
进程的PID同一时刻不可能重复,但可以延迟重用(过一段时间后可以再次使用)。
父子进程的关系:
1 父进程启动子进程后,父子进程同时运行;如果子进程先结束,子进程会给父进程发信号,父进程回收子进程的资源。
2 父进程启动子进程后,父子进程同时运行;如果父进程先结束,子进程变成孤儿进程,认init进程(进程1)为新的父进程。init进程也叫 孤儿院。
3 父进程启动子进程后,父子进程同时运行;如果子进程先结束,但没有发出信号或者父进程没有收到信号,子进程将变成僵尸进程
函数getuid()可以获取当前用户ID。
如何创建子进程?
方法1: fork()创建
方法2: vfork()+execl()合作创建
fork()创建的子进程是通过复制父进程产生的,因此和父进程执行相同的代码段。
vfork()+execl()创建子进程是全新的,执行全新的代码段(不复制父进程的任何东西)。
fork() - 复制父进程,创建子进程
fork()创建子进程时,除了代码区不复制,其他内存区域全部复制父进程的,父子进程共享代码区。
如果父进程中有文件描述符,子进程只复制描述符,不复制文件表。
子进程会复制父进程的缓冲区。
pid_t pid = fork(void);
pid_t 就是进程ID,fork()没有参数,返回进程ID。
fork()之后,父子进程同时运行;谁先运行,谁先结束都不一定。
所有的浮点运算都是 近似值。每个小数(浮点)都是通过算法 得到近似值。
fork()创建的子进程是通过复制父进程产生的,因此和父进程执行相同的代码段。
vfork()+execl()创建子进程是全新的,执行全新的代码段(不复制父进程的任何东西)。
fork() - 复制父进程,创建子进程
fork()创建子进程时,除了代码区不复制,其他内存区域全部复制父进程的,父子进程共享代码区。
如果父进程中有文件描述符,子进程只复制描述符,不复制文件表。
子进程会复制父进程的缓冲区。
pid_t pid = fork(void);
pid_t 就是进程ID,fork()没有参数,返回进程ID。
fork()之后,父子进程同时运行;谁先运行,谁先结束都不一定。
fork()创建的子进程,只执行fork()之后的代码,fork()之前的代码不会执行。
也就是说:fork()之前的代码父进程执行一次 ,fork()之后的代码父子进程分别执行一次,会执行二次。
fork()的返回会执行两次,父进程会返回子进程的PID,子进程返回0。
fork()在复制内存时,父进程虚拟地址子进程照抄,但会去映射不同的物理内存。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
//pid_t pid = fork();//没有复制描述符,两个都是新建
int fd=open("a.txt",O_RDWR|O_CREAT|O_TRUNC,0666);
if(fd == -1) perror("open"),exit(-1);
pid_t pid = fork();//复制描述符,不复制文件表
if(pid == 0){//子进程
write(fd,"abc",3);
close(fd);
fd = 5;//父进程的fd不会改变,因为复制了描述符
exit(0);
}
sleep(1); printf("fd=%d\n",fd);//fd复制了
write(fd,"123",3);//但文件表没有复制
close(fd);
}
进程的终止:
正常终止
1 主函数执行了return语句。
2 用exit()函数退出进程。
3 用_Exit()/_exit()函数退出进程。
4 最后一个线程退出(return方式退)。
5 最后一个线程调用pthread_exit()。
非正常终止
1 收到信号,信号导致进程退(比如:ctlr+c)。
2 最后一个线程被其他线程取消。
_exit()和_Exit()其实是一回事,_Exit()底层调用的就是_exit()。
exit()和_Exit()不同:
exit()不会立即结束进程,会先调用atexit()注册的某些函数然后再结束(慢性子)。同时exit()的参数会与 0377做位与以后再返回。
_Exit()会立即结束进程,同时关闭所有文件描述符,把所有子进程变成孤儿进程,参数直接被返回(急性子)。
exit(int status)中,status 进程自身无法获取(进程自身已经结束),父进程可以获取status,父进程必须保证 子进程先结束才能回收status。
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
pid_t pid =fork();
if(!pid){//子进程执行的分支
printf("子进程开始运行,PID=%d\n",getpid());
sleep(2); printf("子进程结束\n");
exit(100);//退出码 100
}
printf("父进程等待子进程\n");
int res; pid_t retpid = wait(&res);
printf("等到的子进程pid=%d\n",retpid);
printf("res=%d\n",res);
if(WIFEXITED(res)){
printf("正常退出,退出码%d\n",WEXITSTATUS(res));
}
}
wait()/waitpid()可以让父进程等待子进程结束,同时回收 status并且可以判断子进程是否正常结束。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(){
int pid1,pid2; pid1 = fork();
if(pid1 > 0) pid2 = fork();//只有父进程能执行
if(pid1 == 0){//子进程pid1执行
printf("pid1执行,pid=%d\n",getpid());
sleep(3); printf("pid1执行完毕\n");
exit(100); }
if(pid2 == 0){//子进程pid2执行
printf("pid2执行,pid=%d\n",getpid());
sleep(1); printf("pid2执行完毕\n");
exit(200); }
int res;
pid_t retpid = waitpid(-1/*pid1*/,&res,0);//0阻塞
printf("子进程%d结束\n",retpid);
if(WIFEXITED(res))
printf("code=%d\n",WEXITSTATUS(res));
}
pid_t wait(int* status)
wait()会让父进程等待任意子进程结束/挂起,并取得子进程的返回码和结束状态,存入status中,函数的返回值就是结束的子进程PID。wait() 能回收僵尸子进程,因此得名殓尸工。
status里面存了多种数据,因此需要拆分:
WIFEXITED(status) - 判断是否正常结束
WEXITSTATUS(status) - 获取exit()中参数
waitpid()是wait()的增强版,完全覆盖wait()的功能。
pid_t waitpid(pid_t pid,int* status,
int options)
参数:pid 指定等待哪些子进程
-1 等待所有子进程
>0 等待对应pid的子进程(特定)
0 等待和当前进程同组的子进程之一
<-1等待特定组(组ID=PID绝对值)的子进程之一
一般来说,-1 和 大于0 常用。
status和wait()一样
options 如果是WNOHANG,非阻塞等待,如果没有子进程结束,直接返回0而不等待
返回 结束子进程的PID
vfork()+execl()方式
vfork() 负责创建子进程,但不复制任何父进程的内存空间。
execl() 提供新的代码区、全局区、堆区、栈区...,但execl()不会创建新进程。
vfork()创建子进程时,不复制父进程内存,但会直接占用父进程的内存,因此父进程没有资源继续运行,处于阻塞状态,直到子进程结束或者子进程调用了 execl()函数族。
vfork()确保子进程先运行。vfork()必须和execl()函数族结合使用才有意义。
execl()函数 替换进程,原来的进程被和谐,从头开始执行新进程,PID不变。
vfork()负责创建新进程,新进程的代码和数据execl()提供(用替换方式)。
vfork()就用法上来说,和fork()一样。
execl(char* path,char* cmd,...)
path是新程序所在的路径(包括新程序名)
cmd是执行新程序的命令
...是命令的各种参数和选项,可以没有
以NULL做最后一个参数,代表结束。
execl()可以启动 系统程序,可以启动 自定义程序。比如:启动ls -l命令。
execl("/bin/ls","ls","-l",NULL);