进程

进程

【1】进程的概念

1.什么是进程

1. 进程是一个独立的,可以调度的任务。
2. 进程是一个程序的一次执行过程。
3. 进程在调度的时候,系统会分配和释放各种资源(cpu,内存资源......)
4. 进程是一个抽象的概念。

2.进程和程序的区别

程序是静态的,它是保存在磁盘上的可执行二进制文件.
进程是一个动态的概念,它是程序的一次执行过程。包括进程的创建,调度,消亡。是存在内存中

3.进程的内存管理

1. 每个进程都会分配4G的内存空间(虚拟空间)
2. 其中0-3G是用户空间代码使用,3-4G是内核空间使用。
3. 3-4G的内核空间是所有进程共享的。
虚拟地址和物理地址
​	物理地址:硬件上个真正存在的地址。
​	虚拟地址:程序运行后,会有4G的虚拟地址,由物理地址映射到虚拟地址
​			程序中的指针,只能指向到虚拟地址,不能指向到物理地址。
注意:
​	1.每个进程都分配4G的虚拟空间,其中0-3G是用户代码空间使用的。所以每个进程的地址都是互不相通的。
​	每个进程都有自己独立的段:
       1)数据段(全局变量,静态变量,堆)
       2)正文段(存放代码,常量)
​	   3)栈段(局部变量,形参,返回值)
​	2.虚拟内存的目的:cpu能够动态分配内存

物理地址思考一下,既然每个进程都有自己的虚拟空间,那么进程之间如何通信?
使用3-4G共享内存的空间实现。

4.进程是资源分配的最小单位

1. 内存的申请和释放
2. 文件描述符,每个进程最多能打开1024个文件描述符
3. 分配cpu时间片
4. 管理自己的虚拟空间(要用的时候映射到物理地址上)
5. ... ...

5.进程号

操作系统会给每个进程分配一个id号,这个id就是进程号(PID)

1.主要的进程标识符
进程号:PID(process id)
父进程号:PPID(parent process id)
2.PID是进程的唯一标识
3.操作系统在刚启动的时候,会启动三个进程
PID:0 	idle进程  	(操作系统引导程序,创建1号2号进程) ,没有调用fork函数创建的进程。
PID:1 	init进程		初始化内核的各个模块。当内核启动完成,用于收养孤儿进程(它是一个没有父进程进程的进程)
PID:2 	kthreadd 	用于进程间的调度

【2】查看进程

1.查看进程的命令

1)ps -aux 显示进程占计算机的资源
USER   PID  %CPU        %MEM           VSZ  RSS  TTY  STAT  START TIME COMMAND
用户名 进程号 占用cpu资源	占用内存百分比						状态	  消耗cpu时间
2)ps -ajx /ps -ef显示父子进程关系
PPID   	 PID  	PGID   		SID  TTY   TPGID  STAT  UID TIME COMMAND	
父进程号	 进程号	进程所属组id	会话组id	
进程组:若干个进程的集合,称之为进程组。默认情况下,新创建的进程会继承父进程的组id;
会话组:若干个进程组的集合,称之为会话。默认情况下,新创建的进程会继承父进程的会话id;

STAT:

           D    不可被中断的阻塞态
           R    正在运行的进程
           S    处于休眠状态的进程
           T    被挂起/被追踪的进程
           W    进入内存交换的进程(从内核2.6开始无效)
           X    死掉的进程,死亡是一瞬间的事情,不可被捕捉
           Z    僵尸进程
           <    高优先级
           N    低优先级
           L    部分页被锁进内存
           s    会话组组长(有子进程)
           l    多线程
           +    运行在前端
3)pidof 根据程序名获取PID号
$ pidof 	a.out 	---->显示a.out的pid号
4)pstree 显示进程关系树
5)top 实时显示进程状态
$ top 	-d 	秒数 	设置多少秒刷新一次
​$ top   -d   5 		5秒刷新一次
​$ top 	-p 	pid1  		指定显示的进程id
​$ top -p 5627
6)kill 杀死进程
$ kill 	-9	PID 	  根据PID杀死进程
​$ kill 	-9 	6007
$ killall 	-9 	进程名字  根据进程名字杀死所有进程
$ killall 	-9 	a.out	退出所有进程名字为a.out的程序
​$ sudo killall -9 bash 		杀死所有终端

2.进程状态

1. 创建态:创建一个进程,完成资源分配,进入就绪态
2. 就绪态:资源已经准备好,等待cpu调度
3. 运行态:程序被cpu调度,进入运行态
4. 阻塞态:正在运行的程序,由于某些事件无法运行,就会进入阻塞态。一旦事件被满足,就会进入就绪态
5. 终止态:程序结束

进程状态

【3】进程相关的系统调用

man 2卷

1)fork

功能:创建一个子进程;
头文件:
       #include <unistd.h>
原型:
       pid_t fork(void);
返回值:
    成功 	0 	在子进程中,返回0;
    	      >0  在父进程中,返回创建的子进程PID
    失败 -1 	创建失败,更新errno

注意

1. 父进程会拷贝一份资源给子进程(克隆),父子进程的资源是一致的。但是子进程不会执行fork以及fork以上的代码。
2. 子进程会拷贝父进程的文件描述符
3. 子进程拷贝父进程的虚拟地址
   子进程会拷贝父进程的数据段,正文段,堆栈段。
   父子进程的堆栈区的虚拟地址完全一致。但是物理地址不一致。
4. 父子进程是相互独立的两个进程。
5. fork的返回值不可能是1,1为init进程,收养所有孤儿进程的进程

进程的地址

例子
int main(int argc, const char *argv[])
{
	int a = 10;
	pid_t pid  = fork();
//	a = 11;
	if(pid > 0)
	{
		//父进程
		printf("pid=%d this is parent\n",pid);
		printf("pid=%d ppid=%d\n",getpid(), getppid());
		printf("parent a = %d %p\n", a, &a);
		a = 9;
		printf("parent a = %d %p\n", a, &a);
	}
	else if(0==pid)
	{
		//子进程
		printf("pid=%d this is child\n", pid);
		printf("pid=%d ppid=%d\n",getpid(), getppid());
		sleep(1);
		printf("child a = %d %p\n",a,&a );
	}
	else
	{
		perror("fork");
		return -1;
	}
//	while(1);
	return 0;
}

2)getpid / getppid

功能:获取当前进程/父进程的pid;
头文件:
       #include <sys/types.h>
       #include <unistd.h>
原型:
       pid_t getpid(void);
       pid_t getppid(void);
返回值:
    返回当前进程/父进程的pid

3)_exit / exit

i)_exit

man 2 卷

功能:终止进程;清除进程使用的内存空间,销毁其在内核的各种数据结构;
头文件:
       #include <unistd.h>
原型
       void _exit(int status);
参数:
    int status:是一个32整形。可以利用这个参数传递进程终止状态;
    			通常,0代表正常退出,其余数值代表进程异常退出。
    			该参数由 wait/waitpid函数接收。

注意
_exit()函数 终止进程,不会刷新缓冲区,直接销毁缓冲区

ii)exit

man 3 卷

功能:终止进程,并刷新缓冲区;
头文件:
       #include <stdlib.h>
原型:
       void exit(int status);
参数:
    int status:是一个32整形。可以利用这个参数传递进程终止状态;
    			通常,0代表正常退出,其余数值代表进程异常退出。
    			该参数由 wait/waitpid函数接收。

注意
exit()函数 终止进程,会刷新缓冲区

例子
pid_t pid = fork();
	if(pid > 0)
	{
		//父进程
		printf("parent %d\n", getpid());
		int s = 0;
		pid_t c_pid = wait(&s);
		printf("c_pid = %d\n", c_pid);
		printf("s = %d\n", s);
		int status = WEXITSTATUS(s);
		printf("status = %d\n", status);
		while(1)
			sleep(1);
	}
	else if(0 == pid)
	{
		//子进程
		printf("child %d\n", getpid());
		sleep(2);
		//	_exit(1);
		exit(100);
	}
	else
	{
		perror("fork");
		return -1;
	}

4)wait / waitpid

i)wait
功能:阻塞函数,等待子进程退出,并回收子进程资源;
头文件:
       #include <sys/types.h>
       #include <sys/wait.h>
原型:
       pid_t wait(int *status);
参数:
    int *status:获取子进程退出状态,接收exit(status)/_exit(status),中的status
    			 如果不想接收,直接填NULL;
返回值:
    成功,返回退出的子进程的pid;
	失败,返回-1;

注意
1.阻塞等待子进程退出,并回收子进程资源;
2.如果没有子进程,父进程不等待,直接退出。
从wait(&s)的s中提取到exit(status)的status —通过宏函数提取

1)WEXITSTATUS(status);提取子进程终止的状态值;exit(status)中的status的值。
​	int status = WEXITSTATUS(s);
2)WIFEXITED(status);判断子进程是否正常退出;
​		返回1,代表子进程正常退出;
​		返回0,代表子进程异常退出。
ps:想要子进程异常退出,可以利用kill -9 杀死子进程

pid_t wait(int *status)中的status存储着是一个32位的整形,其中[8,15]为(0开始)存放的是exit(status)返回的status;
所以,exit(status)中的status的取值范围是[0, 255];

ii)waitpid
功能:等待指定的进程退出,并回收指定进程的资源;
头文件:
       #include <sys/types.h>
       #include <sys/wait.h>
原型:
       pid_t waitpid(pid_t pid, int *status, int options);
参数:
    pid_t pid:指定要等待的进程;
       < -1   回收指定进程组下的任意进程,指定进程组id = pid的绝对值
       -1     等待当前进程下的任意子进程;
       0      回收当前进程组下的任意子进程;
       > 0    回收指定进程;
	int *status:获取子进程退出状态,接收exit(status)/_exit(status),中的status
    			 如果不想接收,直接填NULL;
	int options:0,阻塞等待,如果指定进程没有退出,则当前进程阻塞等待。
        		WNOHANG,非阻塞等待,就算指定的进程没有退出,当前进程不等待,继续执行后面的代码。
返回值:
        成功:阻塞,返回指定进程pid
        	非阻塞,如果指定进程退出,返回指定进程的pid;
        			如果指定进程没有退出,返回0;
		失败,返回-1;       			

例子
int main(int argc, const char *argv[])
{
	pid_t pid = fork();
	if(pid > 0)
	{
		//父进程
		printf("parent %d\n", getpid());
		int s = 0;
		pid_t c_pid = wait(&s);
		printf("c_pid = %d\n", c_pid);
		printf("s = %d\n", s);
		int status = WEXITSTATUS(s);
		printf("status = %d\n", status);
		int res = WIFEXITED(s);
		printf("res = %d\n", res);
		while(1)
			sleep(1);
	}
	else if(0 == pid)
	{
		//子进程
		printf("child %d\n", getpid());
		/*
		while(1)
		{
			sleep(2);
		}
		*/
		//	_exit(1);
		exit(300);
	}
	else
	{
		perror("fork");
		return -1;
	}
	return 0;
}

小练习

拷贝一张图片,实现父进程拷贝前半部分,子进程拷贝后后半部分。
提示:子进程先sleep(1)。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
    int fd_r,fd_w;
    //以读的方式打开一张图片
    if((fd_r = open("./1.png", O_RDONLY))< 0)
    {
        perror("open");
        return -1;
    }
    //以写的方式打开图片,只写,创建,清空
    if((fd_w = open("2.png", O_WRONLY|O_CREAT|O_TRUNC, 0664))<0)
    {
        perror("open");
        exit(1);
    }
    //计算文件大小
    int size = lseek(fd_r, 0, SEEK_END);
    printf("size=%d\n", size);
    //创建进程
    pid_t pid = fork();
    if(pid>0)
    {
        //父进程拷贝前半部分
        lseek(fd_r, 0, SEEK_SET);
        int i = 0;
        char c = 0;
        int res = 0;
        for(i=0; i<size/2; i++)
        {
            //读一个字符
            res = read(fd_r, &c, 1);
            if(res<0)
            {
                perror("read");
                exit(1);
            }
            //写一个字符
            res = write(fd_w, &c, 1);
            if(res < 0)
            {
                perror("write");
                exit(1);
            }
        }
        printf("前半部分拷贝完毕\n");
    }
    else if(0 == pid)
    {
        //子进程拷贝后半部分
        sleep(1);
        lseek(fd_r, size/2, SEEK_SET);
        lseek(fd_w, size/2, SEEK_SET);
        int i=0, res=0;
        char c=0;
        for(;;)
        {
            //读一个字符
            res = read(fd_r, &c, 1);
            if(res<0)
            {
                perror("read"); 
                exit(1);
            }
            else if(0 == res)
            {
                break;
            }
            //写一个字符
            res = write(fd_w, &c, 1);
            if(res < 0)
            {
                perror("write");
                exit(1);
            }
        }
        printf("后半部分拷贝完毕\n");
    }
    else
    {
        perror("fork");
        return -1;
    }
    //关闭文件描述符
    close(fd_r);
    close(fd_w);
    return 0;
}

【4】linux的特殊进程

1.孤儿进程(orphad)

概念:父进程退出,子进程未退出。子进程被init进程收养,变成init进程的子进程,脱离终端控制;

ctrl + alt + f1 	切换到字符终端(f1~f6均可)
ctrl + alt + f7 	切换到虚拟终端

注意
1.孤儿进程不能使用ctrl+c退出,但是可以用kill -9杀死
2.注意:1号进程(init进程)不能被杀死的。

例子
#include <stdio.h>
#include <unistd.h>   
int main(int argc, const char *argv[])
{
    pid_t pid = fork();
    if(pid > 0)
    {   
    }   
    else if(0 == pid)
    {   
        while(1)
        {
            printf("child\n");
            sleep(1);
        }
    }   
    else
    {   
        perror("fork");
        return -1; 
    }   
    return 0;
}

2.僵尸进程(zombie)

僵尸进程:子进程退出,父进程没有退出,且子进程的资源没有被回收。这时候子进程就是僵尸进程。
危害:

1占用进程号
2占用内存空间,占用物理空间,以及进程控制块等等。

注意

  1. 僵尸进程只能回收,不能被kill;
  2. 父进程退出后,子进程的资源将由内核回收(收尸)。
  3. 如何回收僵尸进程:
    01 wait/waitpid函数回收。缺点:阻塞函数,父进程不能做自己的事情;
    02 用信号的方式回收;
例子
int main(int argc, const char *argv[])
{
	pid_t pid = fork();
	if(pid > 0)
	{
		int i = 0;
		while(i < 4)
		{
			sleep(1);
			//i++;
		}
	}
	return 0;
}

3.守护进程(daemon)

概念:守护进程又称之为幽灵进程;

1)守护进程常常在系统启动时候开始运行,在系统关闭的时候终止。
​2)独立于控制终端,且周期性的执行某个任务或者等待处理某些事件
3)大多数服务器都是由守护进程实现的。
守护进程的创建

神话小说 ——————> 断情绝爱,修炼成神

1)创建孤儿进程 (断绝父子关系)

fork() 创建子进程,退出父进程。

2)创建新的会话 (离开家族,自立门户)

子进程创建新的会话,就不再依附于父进程的会话。
setsid

#include <unistd.h>
       pid_t setsid(void);
成功,返回新的会话;
失败,返回-1,更新errno;
1.创建新的会话,成为会话组组长;
2.创建新的进程组,成为进程组组长;
3.脱离终端控制
3)改变当前孤儿进程的运行目录为’/'目录(百炼成神)

chdir

  #include <unistd.h>
       int chdir(const char *path);
参数:
    char *path:指定修改后的运行目录里;
返回值:
    成功,返回1;
    失败,返回0;

注意:从这一步开始,往后的程序,就是运行在根目录下的。

4)重设文件掩码 (重塑金身)

守护进程对文件进行权限设置,一般保留文件原有的权限 umask(0);

5)关闭文件描述符(斩断红尘)

子进程拷贝了父进程的文件描述符,需要将文件描述符关闭。包括0,1,2;

例子
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
    pid_t pid = fork();
    if(pid > 0)
    {
    }
    else if(0 == pid)
    {
        //创建新的会话
        if(setsid()<0)
        {
            perror("setsid");
            return -1;
        }
        //修改运行目录
        if(chdir("/") == -1)
        {
            perror("chdir");         
            return -1;
        }
        //重置文件权限掩码
        umask(0);
        //关闭文件描述符
        int fd = 0;
        for(fd=0; fd<getdtablesize(); fd++)
        {
            close(fd);
        }
        while(1)
        {
            sleep(1);
        }
    }
    else
    {
        perror("fork");
        return -1;
    }
    return 0;
}

【5】exec函数族

功能:(夺舍,魂穿)

1)执行一个可执行程序,代替当前进程;
​2)调用函数族后,没有生成新的进程,而是替换了原有的进程正文;
​3)调用前后,进程的pid不变,单执行的是调用后的进程。
#include <unistd.h>
       extern char **environ;
       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 execvpe(const char *file, char *const argv[], char *const envp[]);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值