目录
什么是程序,什么是进程,有什么区别
- 程序是静态的概念,
gcc xxx.c -o pro
磁盘中生成pro文件叫做程序 - 进程是程序的一次运行活动,通俗点意思就是程序跑起来了,系统中就多了一个进程
- 区别就是是否运行了起来
如何查看系统中有哪些进程
- 使用
ps
指令查看,详细的查看需要配合grep
指令,ps -aux | grep
- 使用
top
指令查看,类似于win任务管理器
什么是进程标识符
- 每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证
Pid=0
:交换进程,作用是进程调度Pid=1
:init进程,作用是系统初始化
- 编程调用
getpid(
)函数获取自身的进程标识符 getppid()
获取父进程的进程标识符
什么叫父进程,什么叫子进程
假设:进程A创建了进程B
则 A叫做父进程,B叫做子进程,父子进程是相对概念,理解为人类中的父子关系
C程序的存储空间是如何分配的
fork()函数
创建一个子进程,同时将父进程的许多参数复制给子进程int a=fork()
:这段表达式,如果在父进程中a=子进程的pid,如果在子进程中a=0
fork()小应用:通过scanf()阻塞主进程等待输入,如果输入1,那么创建一个子进程,子进程不断的执行相应的while
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
pid = getpid();
int data=0;
while(1){
printf("current process id:%d\n",pid);
printf("scanf 1 creat process\n");
scanf("%d",&data);
if(data==1){
pid = fork();
if(pid==0){
while(1){
printf("child pid=%d\n",getpid());
sleep(3);
}
}
}else {
printf("wait,do nothing");
}
}
return 0;
}
小问题:
int main()
{
pid_t pid;
pid = getpid();
printf("fork=======\n");//注意这里
int fork_pid = fork();
printf("pid:%d, getpid=%d, fork_pid=%d\n",pid,getpid(),fork_pid);
return 0;
}
int main()
{
pid_t pid;
pid = getpid();
printf("fork=======");//注意这里
int fork_pid = fork();
printf("pid:%d, getpid=%d, fork_pid=%d\n",pid,getpid(),fork_pid);
return 0;
}
这两段代码的不同之处只是printf("fork=======");
和printf("fork=======\n")
;的区别,但是最后的输出结果是不同的,
printf("fork=======")
printf("fork=======\n")
原因在于(AI真方便)
换句话说当子进程创建后,子进程会从被创建的行开始执行,并不是从第一行开始执行,而子进程创建之后,会将父进程的各种状态复制一遍,包括了缓冲区。由于printf("fork=======")
满足了不刷新缓冲区的条件,所以不会立即刷新缓冲区,所以此时缓冲区内保存着一份fork=======
,当子进程创建后,子进程的缓冲区内也保存了一份来自于父进程的fork=======
,但这两者的缓冲区是独立的,所以才出现了上面输出的不同
vfork()创建进程
与fork()的不同
- vfork()直接使用父进程存储空间,不拷贝
- vfork()保证子进程先运行,当子进程调用exit()推出后,父进程才执行
进程退出
正常退出
- main()调用return
- 进程调用
exit()
:标准C库,会去先对缓冲区做一些处理再退出 - 进程调用
_exit()
或者_Exit()
,属于系统调用:不会对缓冲区进行处理_exit()
和_Exit()
是对exit()
的封装
- 进程最后一个线程返回
- 最后一个线程调用
pthread_exit
异常退出
- 调用
abort()
- 当进程收到某些信号时,如
ctrl+c
- 最后一个线程对取消
(cancellation)
请求做出响应
:::info
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符。释放它所使用的存储器等。
对于上述任意一种种植情况,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit,_exit,_Exit),实现这一点的方法是,将其退出状态左为参数传递给函数,在异常终止情况下,内核产生一个指示其异常终止原因的终止状态。在任意一种情况下,该终止进程的父进程都能用wait()或waitpid()取得其终止状态
:::
等待子进程退出
父进程等待进程退出并收集进程的退出状态
如果不收集进程的退出状态,那么子进程就会变成僵尸进程
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data=0;
pid = vfork();
if(pid>0){
while(1){
printf("data=%d\n",data);
printf("current father pid=%d\n",getpid());
sleep(1);
}
}else if(pid==0){
while(1){
printf("current chilid printf:pid=%d\n",getpid());
sleep(1);
data++;
if(data==3){
exit(0);
}
}
}
return 0;
}
等待3s后,通过命令:ps -aux | grep a.out
查看如下
如何收集进程的退出状态
wait(int* status)
:如果其所有子进程都还在运行,则阻塞- status参数为一个整型数指针
- 如果非空:子进程退出状态放在它所指向的地址中
- 空:不关心退出状态
- status参数为一个整型数指针
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data=0;
int status=10;
pid = vfork();
if(pid>0){
wait(&status);//阻塞等待
printf("status:%d\n",WEXITSTATUS(status));
while(1){
printf("data=%d\n",data);
printf("current father pid=%d\n",getpid());
sleep(1);
}
}else if(pid==0){
while(1){
printf("current chilid printf:pid=%d\n",getpid());
sleep(1);
data++;
if(data==3){
exit(100);
}
}
}
return 0;
}
waitpid(pid_t pid, int* status, int options)
:如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回- 参数解析:status和wait()中的status一样
对于pid参数,其等待的ID可以通过vfork()的返回值得到
int main()
{
pid_t pid;
int data=0;
int status=10;
pid = fork();//1
//pid=vfork();
if(pid>0){
printf("father vfork_pid=%d\n",pid);
waitpid(pid,&status,WNOHANG);//2
printf("status:%d\n",WEXITSTATUS(status));
while(1){
printf("data=%d\n",data);
printf("current father pid=%d\n",getpid());
sleep(1);
}
}else if(pid==0){
while(1){
printf("pid=%d\n",pid);
printf("current chilid printf:pid=%d\n",getpid());
sleep(1);
data++;
if(data==5){
exit(100);
}
}
}
return 0;
}
-
如果1处调用的是
pid=fork()
,2处调用的是waitpid(pid,&status,WNOHANG);
,那么程序实际上会并行运行,但依然会造成僵死进程,这里我猜测应该是父进程运行到waitpid()时,子进程并没有退出,waitpid()无法收集,运行过去后,子进程运行完毕,但是父进程已经进入了while(),所以子进程变为了僵死进程 -
如果1处调用的是
pid=vfork()
,2处调用的不变,程序实际上并不会并行运行,依然会阻塞运行,但不会造成僵死进程 -
waitid(idtype_t idtype, id_t id, siginfo_t* infop, int options)
:如果它没有任何子进程,则立即出错返回 -
孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己,此时子进程叫做孤儿进程,Linux避免系统存在过多的孤儿进程,init进程收留孤儿进程,变成孤儿进程的夫进程
exec族函数
//主进程文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
pid = getpid();
int data=0;
while(1){
printf("current process id:%d\n",pid);
printf("scanf 1 creat process\n");
scanf("%d",&data);
if(data==1){
pid = fork();
if(pid==0){
if(execl("./echoarg","./echoarg","TEST.config",NULL) == -1){
printf("execl failed\n");
perror("why");
}
printf("after execl\n");
}
}else {
printf("wait,do nothing");
}
}
return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char** argv)
{
printf("%s\n",argv[0]);
printf("%s\n",argv[1]);
//read src
int fds_src = open(argv[1],O_RDWR);
int fds_src_size = lseek(fds_src,0,SEEK_END);
//move head
lseek(fds_src,0,SEEK_SET);
char* buf = (char*)malloc(sizeof(char)*fds_src_size);
read(fds_src,buf,fds_src_size);
lseek(fds_src,0,SEEK_SET);
char* p = strstr(buf,"two=");
p=p+strlen("two=");
*p='8';
write(fds_src,buf,fds_src_size);
close(fds_src);
return 0;
}
system()函数
实际上system()要比exec族函数要简单粗暴许多
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
pid = getpid();
int data=0;
while(1){
printf("current process id:%d\n",pid);
printf("scanf 1 creat process\n");
scanf("%d",&data);
if(data==1){
pid = fork();
if(pid==0){
if(system("./echoarg TEST.config") == -1){
printf("execl failed\n");
perror("why");
}
printf("after execl\n");
}
}else {
printf("wait,do nothing");
}
}
return 0;
}
在system()中只需要这样调用system("./echoarg TEST.config")
=execl("./echoarg","./echoarg","TEST.config",NULL)
popen():获取运行的输出结果
FILE *popen(const char *command, const char *type);
:第一个参数是指令,第二个参数是指定是读取还是写入模式。如果是r模式,该函数会将第一个参数的结果放到管道中,然后返回一个流,通过FILE流将数据读取出来