进程

1.程序和进程:
1.1 程序:

磁盘中存储的代码文件,是静态的。

1.2 进程:

程序一次执行的过程,是动态的。(程序执行和资源管理的最小单位)
主要进程标识
  进程号 (PID)
  父进程号(PPID)
  PID唯一的标识一个进程
Linux中的进程包含三个段
  数据段:存放全局变量,常数以及动态数据分配的数据空间
  正文段:存放的是程序中的代码
  堆栈段:存放的是函数的返回地址,函数的参数以及程序中的局部变量
进程类型:  
  交互进程:由shell控制和运行的。可前台可后台
  批处理进程:不属于某个终端,它被提交到一个队列中以便顺序执行
  守护进程:在后台运行。一般在LINUX启动时开始执行,系统关闭时才结束

2.linux调度进程相关命令:
			at 			在指定时刻执行相关进程
			cron		周期性执行相关进程
			ps 			查看系统中的进程
				ps -ef 			显示系统下所有进程
			top			动态显示系统中的进程
			nice		按用户指定的优先级运行进程
				./a.out -n 19	(用户指定最高优先级0 最低优先级19)
			renice		改变正在运行进程的优先级
				renice -n 3 3632(PID)
			kill		向进程发信号
				kill -9			强制杀死进程
			bg			将挂起的进程在后台执行
				bg 1 			(1为进程挂起序号,Ctrl+z后中括号内的数字)
			fg			将挂起的进程放到前台运行
				fg 1 			(1为进程挂起序号,Ctrl+z后中括号内的数字)
			&			让程序后台运行
				./a.out & 		直接后台运行a.out
3.常用函数
3.1 进程创建:fork()
	pid_t fork(void);
	功能:创建子进程
	头文件:	
			#include<sys/types.h>
			#include<unistd.h>
	返回值:	-1:			出错
			0 :			成功,代表子进程区域
			子进程PID:	成功,代表父进程区域
			

例子:

		int main()
		{
			printf("main:%d",getpid());
			pid_t pid = fork();//创建子进程
			if(pid < 0)
			{
				perror("fork");
				return -1;
			}else if(pid == 0){//子进程
				printf("child process:%d\n",getpid());
			}else{ //父进程
				printf("pid = %d\n",pid);
				printf("farther process:%d\n",getpid());
			}
		}
		运行结果:
			main:3812
			pid = 3813
			farther process:3812
			child process:3813

【注】:
①孤儿进程:父进程 先于 子进程结束,子进程就成为孤儿进程。
孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
②僵尸进程:子进程 先于 父进程结束,父进程没有回收子进程的资源。
子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。

③fork创建子进程时,完整的拷贝父进程的资源,父子进程间的同名数据互不干扰
fork之后父进程内定义的变量在子进程中没有定义。(fork只拷贝fork之前父进程的资源)

④现代unix版的fork也创建新进程,只有子进程需要改变内存中数据时才拷贝父进程。这叫“写操作时拷贝”

3.2 exec函数族:

  exec函数提供了一种在进程中启动另一个程序执行的方法,可以根据指定的文件名或目录名找到可执行文件,并用它取代原调用进程的数据段,代码段和堆栈段。除了进程号,其他全部都被替换(相当于夺舍了,哪怕这个夺舍程序结束了,也不会在执行被夺舍的子进程后面的代码)
	头文件:	
				#include<unistd.h>
	返回值:		-1 :出错
				成功不会返回
	函数原型:	int execl(const char *path,const char *arg,...);
				int execv(const char *path,char *const argv[]);
				int execle(const char *path,const char *arg,...,char *const envp[]);
				int execve(const char *path,char *const argv[],char *const envp[]);
				int execlp(const char *file,const char *arg,...);
				int execvp(const char *file,const char *argv[]);
				

例子:

		int main()
		{
			/*调用execlp函数,相当于调用了"ps -ef" 命令*/
			if(execlp("ps","ps","-ef",NULL) < 0)
			{
				perror("execlp error!");
			}
			return 0;
		}
【注】 
		execl/execv:必须完整地给出 可执行文件或脚本文件的路径
		execlp/execvp:自动搜索 可执行文件或脚本文件的路径
3.3 exit结束进程:
	函数原型:	void exit(int status); //c库函数,结束时会刷新缓冲区
	头文件: 	
				#include<stdlib.h>
	
	函数原型: 	void _exit(int status);//系统调用,不会刷新缓冲区
	头文件:	
				#include<unistd.h>
	
	参数:
		status: 是一个整型参数,可以利用这个参数传递进程结束时的状态。
				通常0表示正常结束,其他数值表示出现了错误,进程非正常结束。
				可以用wait系统调用接收子进程的返回值,进行相应的处理。
		
		(1)WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
		
		(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数--指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。)
		例如:
			int status;
			pid_t cid = wait(&status);
			printf("status = %d\n",WIFEXITED(status));  //子进程正常退出为非零  非正常退出为零
		
		(2)WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值
					如果子进程调用exit(5)退出,WEXITSTATUS(status)就会返回5;
					如果子进程调用exit(7)退出,WEXITSTATUS(status)就会返回7。
					请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义。
3.4 wait和waitpid回收进程:
wait()
	函数原型:	pid_t wait(int *status)
	功能:		调用该函数使进程阻塞,直到任一个子进程结束或者是该进程收到了
				一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait
				函数会立刻返回
	头文件:		
				#include<sys/types.h>
				#include<sys/wait.h>
	函数参数:	
				status是一个整型指针,指向的对象用来保存子进程退出时的状态。
				1)status若为空,表示忽略子进程退出时的状态
				2)status若不为空,表示保存子进程退出时的状态
	返回值:	成功:子进程的进程号
			失败:-1
waitpid()	
	函数原型:	pid_t waitpid(pid_t pid,int *status,int options);
	功能:		功能和wait函数类似。可以指定等待某个子进程的结束以及等待方式
				(阻塞或非阻塞)
	头文件:		
				#include<sys/types.h>
				#include<sys/wait.h>
	函数参数:	
				pid:	pid>0:	只等待进程ID等于pid的子进程。
						pid=-1:	等待任何一个子进程退出,此时和wait作用一样
						pid=0:	等待其组ID等于调用进程的组ID的任一子进程
						pid<-1:	等待其组ID等于pid绝对值的任一子进程
				status:	同wait
				options:WNOHANG:若由pid指定的子进程不立即可用,则waitpid不阻塞,此时返回值为0
						0:		阻塞父进程,等待子进程退出
	返回值:	正常:结束的子进程的进程号
			使用选项WNOHANG且没有子进程结束时:0
			出错:-1
4.守护进程
ps -axj 可以查看守护进程

步骤
1.创建子进程,父进程退出
2.在子进程中创建新会话
 setsid函数:创建一个新的会话,并使当前进程成为新会话组的组长
3.改变当前目录为根目录
 通常做法是让“/”或“tmp”作为守护进程的当前工作目录。
 在进程运行过程中,当前目录所在的文件系统是不能卸载的。
 chdir函数可以改变进程当前工作目录
4.重设文件权限掩码
 文件权限掩码是指文件权限中被屏蔽掉的对应位。把文件权限掩码设置为0,
 可以增加该守护进程的灵活性。
 设置文件权限掩码的函数使umask
 通常的使用方法为umask(0)
5.关闭文件描述符

			fdtablesize = getdtablesize();
			for(fd = 0;fd<fdtablesize;fd++)
			{
				close(fd);
			}

范例:

		#include<stdio.h>
		#include<sys/types.h>
		#include<unistd.h>
		
		#include<stdlib.h>
		#include<sys/stat.h>
		int main()
		{
			//1.创建孤儿进程
			pid_t pid = fork();
			if(pid<0)
			{
				perror("fork");
				return -1;
			}else if(pid > 0)
			{
				exit(0);
			}else{
				//2.创建新的会话
				if(setsid()<0)
				{
					perror("setsid");
				}
				//3.更改工作目录
				chdir("/tmp");
				//4.设置文件权限掩码
				umask(0);
				//5.关闭多余文件描述符
				int fdtablesize = getdtablesize();
				int fd;
				for(fd = 0;fd<fdtablesize;fd++)
				{
					close(fd);
				}
			}
		}
5.进程间通信方式
5.1传统的进程间通信方式

无名管道(pipe),有名管道(fifo),信号(signal)

5.1.1 无名管道(pipe)
  1. 只能用于具有亲缘关系的进程之间的通信,半双工的通讯模式,具有固定的读端和写端
  2. 管道可以看成一种特殊的文件,对于它的读写可以使用文件IO如read,write函数。
  3. 当一个管道创建时,会创建两个文件描述符fd [0] (读管道),fd [1] (写管道)
  • 创建无名管道 pipe
函数原型int pipe(int fd[2]);
头文件#include<unistd.h>
函数参数fd:包含两个元素的整型数组
函数返回成功:0
失败:-1

【注】 :

  1. 管道中无数据时,读操作会阻塞
  2. 管道缓冲区有空闲区域,写进程会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,写操作会一直阻
  3. 管道读端存在时,向管道写入数据才有意义,否则向管道写入数据的进程将受到内核传来的SIGPIPE信号(管道破裂
    信号)
  • 代码示例
	/*子进程向管道写入数据,主进程从管道读取数据*/
	#include<stdio.h>
	#include<stdlib.h>
	#include<string.h>
	#include<strings.h>
	#include<unistd.h>
	int main()
	{
		int fd[2];//fd[0]为写端,fd[1]为读端
		if(pipe(fd)<0)//在内核中创建 无名管道
		{
			perror("pipe");
			return -1;
		}
		pid_t pid;
		if((pid = fork())<0)
		{
			perror("fork");
			return -1;
		}else if(pid == 0){
			char buf[32];
			close(fd[0]);//关闭读端
			while(1){
				memset(buf,0,sizeof(buf));
				fgets(buf,sizeof(buf),stdin);
				write(fd[1],buf,strlen(buf));
			}
		}else{
			char buf[32];
			close(fd[1]);//关闭写端
			while(1){
				memset(buf,0,sizeof(buf));
				read(fd[0],buf,sizeof(buf));
				printf("buf = %s\n",buf);
				if(strstr(buf,"quit") != NULL){
//字符串查找函数,在buf地址上查找"quit"字符串,如果找到,返回子串首字母地址,否则返回NULL
					//exit(0);
					printf("are you okey!?\n");
				}
			}

		}
		return 0;
	}
5.1.2 有名管道(FIFO)
  1. 可以使互不相关的两个进程互相通信。可以通过路径名来指出,并且在 文件系统中可见。
  2. 进程通过文件IO来操作有名管道。
  3. 有名管道遵循先进先出规则。
  4. 不支持如lseek的操作。
  • 创建有名管道
函数原型int mkfifo(const char *filename,mode_t mode);
头文件#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
函数参数filename:要创建的管道
mode:指定创建的管道访问权限,8进制表示
函数返回成功:0
失败:-1
  • 代码示例
/*创建管道*/
int res = mkfifo("./5.fifo.txt", 0666);
    if(res == -1)
    {
        perror("mkfifo");
        exit(1);
    }

/*write进程*/
	#include<stdio.h>
	#include<stdlib.h>
	#include<string.h>
	#include<strings.h>
	#include<unistd.h>
	#include<fcntl.h>
	#include<sys/types.h>
	#include<sys/wait.h>
	int main()
	{
		int fd = open("./5.fifo.txt",O_WRONLY); //只写打开管道文件
		if(fd<0){
			perror("open");
			return -1;
		}
		pid_t pid;
		if((pid = fork())<0){
			perror("fork");
			return -1;
		}else if(pid == 0){
			char buf[32];
			while(1)
			{
				memset(buf,0,sizeof(buf));
				fgets(buf,sizeof(buf),stdin);
				write(fd,buf,sizeof(buf));  //向管道文件写数据
				if(strstr(buf,"quit")!=NULL){
					exit(0);
				}
			}
		}else{
			wait(NULL);
			close(fd);
			printf("over\n");
		}
		return 0;
	}

/*read进程*/
	#include<stdio.h>
	#include<stdlib.h>
	#include<string.h>
	#include<strings.h>
	#include<unistd.h>
	#include<fcntl.h>
	#include<sys/types.h>
	#include<sys/wait.h>
	int main()
	{
		int fd = open("./5.fifo.txt",O_RDONLY);//只读打开管道文件
		if(fd<0){
			perror("open");
			return -1;
		}
		pid_t pid;
		if((pid = fork())<0){
			perror("fork");
			return -1;
		}else if(pid == 0){
			char buf[32];
			memset(buf,0,sizeof(buf));
			while(1)
			{
				if(read(fd,buf,sizeof(buf))>0) //从管道读取数据
				{
					printf("buf = %s\n",buf);
					if(strstr(buf,"quit")!=NULL){
						exit(0);
					}
					memset(buf,0,sizeof(buf));
					
				}
			}
			
		}else{
			wait(NULL);
			close(fd);
			printf("over\n");
		}
		return 0;
	}
5.1.3 信号(SIGNAL)
  • SIGNAL特点
  1. 软件层次上对中断机制的一种模拟,是一种异步通信方式
  2. 信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了那些系统事件
  3. 如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它
  4. 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,至到其阻塞被取消时才被传递给进程
  • 信号的生存周期
    信号产生(内核进程)—>信号注册(用户进程)—>信号注销(用户进程)

  • 用户进程对信号的响应方式
    忽略信号:对信号不做任何处理,但SIGKILL和SIGSTOP不能忽略
    捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数(也叫注册信号)
    执行缺省操作:LINUX对每种信号都规定了默认操作

  • 使用信号的场合
    后台进程需要使用信号,如xinetd
    如果两个进程没有亲缘关系,无法使用无名管道
    如果两个通讯进程之一只能使用标准输入和标准输出,则无法使用FIFO

  • kill()和raise()信号发送函数

    kill函数同kill系统命令一样,可以发送信号给进程或进程组(kill系统命令指示kill函数的一个用户接口)

    kill -l命令查看系统支持的信号列表

    数原型int kill(pid_t pid,int sig);
    文件#include<signal.h>
    #include<sys/types.h>
    数功能向某个进程发送信号
    数参数pid:正数:要接受信号的进程的进程号
       0: 信号被发送到所有和pid进程在同一进程组的进程
       -1: 信号发送给所有的进程表中的进程(除了进程号最大的进程外)
    sig:信号
    数返回成功:0
    失败:-1
    	#include<stdio.h>
    	#include<signal.h>
    	#include<sys/types.h>
    	#include<unistd.h>
    	int main()
    	{
    		//向某个进程发送一个信号
    		kill(getpid(),SIGKILL);//等价于raise(SIGKILL);
    		printf("hello world\n");
    		return 0;
    	}
    

    raise函数是进程向自己发送信号

    数原型int raise(int sig);
    文件#include<signal.h>
    #include<sys/types.h>
    数功能进程向自己发送信号
    数参数sig:信号
    数返回成功:0
    失败:-1
  • alarm()闹钟函数

    数原型unsigned int alarm(unsigned int seconds);
    文件#include<unistd.h>
    数功能在进程中设置一个定时器。当定时器指定的时间到时,内核就向进程发送SIGALARM信号
    数参数seconds:指定秒数
    数返回成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟剩余时间,否则返回0
    出错:-1
  • pause()挂起函数

    数原型int pause(void);
    文件#include<unistd.h>
    数功能用于将调用进程挂起直到受到信号为止
    数返回-1,并且吧error设置为EINTR
  • signal()信号处理函数

    数原型void (*signal(int signum,void(*handler)(int)))(int);
    文件#include<signal.h>
    数功能指定要处理的信号和处理函数,对信号接受并处理
    数参数signum: 指定信号
    handler: SIG_IGN:忽略该信号
        SIG_DFL:采用系统默认方式处理信号
        自定义的信号处理函数指针
    数返回成功:设置之前的信号处理方式(返回值是一个void(*)(int)的函数指针)
    出错:-1
    	#include<stdio.h>
    	#include<stdlib.h>
    	#include<string.h>
    	#include<strings.h>		
    	#include<signal.h>
    	#include<unistd.h>
    	void myfunc(int signo)
    	{
    		if(signo == SIGINT){
    			printf("you pressed ctrl+c\n");
    		}else if(signo == SIGTSTP){
    			kill(getpid(),SIGKILL);
    		}
    	}
    	int main()
    	{
    		signal(SIGINT,myfunc);//注册信号和信号处理
    		signal(SIGTSTP,myfunc);
    		while(1);
    		return 0;
    	}
    
5.2 System V IPC对象:

IPC对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值