• linux进程大杂烩==Linux应用编程4


    一、程序的开始和结束

    1.main 函数由谁调用

    • (1)编译链接时的引导代码:操作系统其实在执行 main 之前也需要执行一段引导代码,编译链接时由链接器将编译器中事先准备好的引导代码给链接进去,跟我们的应用程序构成最终的可执行程序。
    • (2)运行时的加载器:当我们执行一个程序时,加载器负责将代码加载到内存中去执行。
    • (3)程序在编译链接时用链接器(链接引导代码),运行时用加载器(加载到内存)
    • (4)argc 和 argv 的传参的实现涉及到引导代码、加载器。

    2.程序如何结束
    -(1)正常终止:return、exit、_exit。
    -(2)非正常终止:自己或他人发送非正常终止信号终止进程。

    3.atexit 注册进程终止处理函数

    • (1)atexit 注册多个进程终止处理函数,先注册的后执行(与栈类似)。
    • (2)return、exit 和_exit 的区别:return 和 exit 效果一样,都会执行进程终止处理函数,但 _exit 终止进程时不会执行进程终止处理函数。
      #include 
      #include 
      #include 
      void func1(void)
      {
      	printf("func1\n");
      }
      void func2(void)
      {
      	printf("func2\n");
      }
      int main(void)
      {
      	printf("hello world.\n");
      	//当进程被正常终止时,系统会自动调用这里注册的func1执行
      	atexit(func2);//注册进程终止处理函数2
      	atexit(func1);//注册进程终止处理函数1
      	printf("my name is rgd.\n");
      	//return 0;
      	//exit(0);
      	_exit(0);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22

    二、进程环境

    1、环境变量

    • (1)export 命令可查看环境变量。
    • (2)进程环境表:每一个进程都有一份所有环境变量构成的表格,当前进程中可以直接使 用这些环境变量。进程环境表其实是一个字符串数组,用 environ 变量指向它。
    • (3)程序中通过 environ 全局变量使用环境变量。
    • (4)我们写的程序可以无条件地使用环境变量,一旦用到了环境变量,那么程序就和操作系统有关了。
    • (5)获取指定环境变量函数:getenv。
      #include 
      int main(void)
      {
      	extern char** environ;//声明即可使用,但是此变量是在外部定义的
      	int i=0;
      	while(NULL!=environ[i]){
      		printf("s%\n",environ[i]);i++
      	}
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    2、进程运行的虚拟地址空间

    • (1)操作系统中每个进程在独立的地址空间中运行。
    • (2)每个进程的逻辑地址空间均为 4GB(32 位系统),其中 0~1G 为 OS 占用,1~4 为应用 程序占用。
    • (3)操作系统会完成虚拟地址到物理地址的映射。
    • (4)意义:①进程隔离;②提供多进程同时运行的环境。

    三、进程登场

    1、什么是进程

    • (1)进程就是程序的一次运行过程,它是一个动态过程而不是静态实物。
    • 进程控制块(PCB)是内核中专门用来管理一个进程的数据结构。

    2、进程 ID

    • (1)进程 ID 用来唯一标识一个进程,便于管理,位于 PCB 中。
    • (2)常用获取进程 ID 的函数:
      • ①获取当前进程 ID:getpid();②获取当前进程的父进程 ID:getppid();③获取用户 ID:getuid();④获取有效用户 ID:geteuid();⑤获取组 ID:getgid();⑥获取有效组 ID:getegid()。
      #include 
      #include 
      #include 
      int main(void)
      {
      	pid_t p1=-1,p2=-1;
      	printf("helloworld!\n");
      	p1=getpid();printf("pid = %d.\n", p1);
      	p2 = getppid();printf("parent id = %d.\n", p2);
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

    3、多进程调度原理

    • (1)操作系统通过合理调节同时运行多个进程,宏观上看起来是并行,微观上是串行。单核cpu时间片轮转
    • (2)进程是资源分配的最小单位,线程是程序执行的最小单位。

    四、fork 系统调用创建子进程

    1、为什么要创建子进程

    • (1)每一次程序的运行都需要一个进程,需要创建多个进程实现宏观上的并行。

    2、fork系统调用的内部原理

    • (1)进程的分裂生长模式:如果操作系统需要一个新进程来运行一个程序,那么操作系统 就会用一个现有的进程来复制生成一个新进程,老进程叫父进程,新进程叫子进程。
    • (2)fork 系统调用调用一次会返回两次,使用fork后要用if判断返回值,返回值等于0的就是子进程,返回值大于0的就是父进程。
    • (3)fork 的返回值在父进程中等于本次创建的子进程的进程 ID,在子进程中等于 0;
      #include 
      #include 
      #include 
      int main(void)
      {
      	pid_t p1=-1;
      	p1=fork();//返回两次
      	if(p1==0){//是子进程
      		sleep(1);//先sleep一下让父进程先运行,先死
      		printf("子进程, pid = %d.\n", getpid());
      		printf("子进程, 父进程 ID = %d.\n", getppid());
      	}
      	else if(p1>0){//是父进程
      		printf("父进程, pid = %d.\n", getpid());
      		printf("父进程, p1 = %d.\n", p1);
      	}
      	else ;// 这里一定是 fork 出错了
      	//=======在这里的操作会被执行两遍
      	//printf("hello world,pid=%d.\n",getpid());
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21

    在这里插入图片描述
    3、关于子进程

    • (1)子进程由父进程复制而来,有自己独立的 PCB,被内核同等调度。享受独立的地址空间

    五、父子进程对文件的操作

    1、子进程继承父进程中打开的文件(同一个fd)

    • (1)测试上下文:父进程先打开一个文件得到 fd,然后再 fork 创建子进程,之后在父子进程中各自用 write 向 fd 写入内容。结果为接续写,原因是父子进程之间的 fd 对应的文 件指针是彼此关联的(类似 O_APPEND)。
    • (2)实际测试时有时候会只看到某一进程写入的内容,原因是该进程开始之前,另一进程已经结束了,导致文件被强行关闭,该进程打开后又因为 O_TRUNC 将内容清空了。

    2、父子进程各自打开文件实现文件共享(不同fd)

    • (1)测试上下文:父进程 open 打开 1.txt 然后写入,子进程 open 打开 1.txt 然后写入。结 果为分别写,原因是父子进程分离后才各自打开文件,这时候两个 PCB 已经相互独立文件表也独立了,因此两次读写是完全独立的。
    • (2)若在上述测试中 open 时加入 O_APPEND,则可以把父子进程的文件指针关联起来,实 现接续写。

    3、conclusion

    • (1)父子进程会有关联:父进程在没有 fork 之前自己做的事情对子进程有很大的影响,但是父进程 fork 之后自己在 if 里面做的事情就对子进程没有影响了。本质原因是 fork 内部实际上已经复制了父进程的 PCB 生成了子进程的 PCB,两个进程已经独立被OS调度运行。
    • (2)我们创建子进程的最终目的是要去独立运行其他程序,否则创建子进程没有意义。
    • 不同fd
      #include 
      #include 
      #include 
      #include 
      #include 
      #include 
      int main(void)
      {
      	int fd=-1;
      	pid_t pid=-1;
      	//============fork个子进程
      	pid=fork();
      	if(pid>0){//parent
      		printf("parent.\n");
      		fd=open("1.txt",O_RDWR|O_APPEND);
      		if(fd<0){
      			perror("open");return -1;
      		}
      		write(fd,"hello",5);
      		sleep(1);
      	}
      	else if(pid==0){//child
      		printf("child.\n");
      		fd=open("1.txt",O_RDWR|O_APPEND);
      		if(fd<0){
      			perror("open");return -1;
      		}
      		write(fd,"world",5);
      		sleep(1);
      	}
      	else{
      		perror("fork");exit(-1);
      	}
      	close(fd);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
    • 相同fd
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main()
    {
    	int fd=-1;
    	pid_t pid=-1;
    	fd = open("1.txt", O_RDWR | O_TRUNC);//父子进程同一个fd,默认续写
    	if(fd<0){
    		perror("open");return -1;
    	}
    	pid=fork();
    	if(pid>0){//父亲
    		printf("parent.\n");
    		write(fd, "hello", 5);sleep(1);
    	}
    	else if(pid==0){//儿子
    		printf("child.\n");
    		write(fd, "world", 5);sleep(1);
    	}
    	else{//失败
    		perror("fork");exit(-1);
    	}
    	close(fd);return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    六、进程的诞生和消亡

    1、进程的诞生

    • (1)进程 0 和进程 1:进程 0 是由内核构建的,进程 1 是由进程 0 fork 创建而来的。
    • (2)其他进程:都是由 fork 或者 vfork 创建而来的。

    2.进程的消亡

    • (1)正常终止和异常终止:指进程正常运行然后由自己主动结束和被其他程序被动结束。
    • (2)进程运行是需要消耗系统资源(内存、IO),进程终止时理应完全释放这些资源,如果没有释放这些资源就丢失了。
    • (3)Linux 系统设计时规定:每一个进程退出时,操作系统将自动回收这个进程涉及到的所有资源(malloc、open),但是只回收了内存和 IO,并没有回收这个进程本身占用的内存(8KB,主要是 task_struct 和栈内存)
    • (4)每一个进程都需要一个来帮它释放本身占用的内存的进程,这个进程就是它的父进程。

    3、僵尸进程

    • (1)僵尸进程即子进程先于父进程结束,但是父进程暂时还没有帮其释放资源,此时子进程成为僵尸进程。但子进程除 task_struct 和栈内存外其他内存空间皆已被清理。(操作系统会自动回收进程所涉及到的内存和IO资源)
    • (2)父进程可以使用 wait 或 waitpid 以显式方式回收子进程剩余资源并获取子进程退出状 态。
    • (3)若父进程不显式回收子进程,当父进程结束时一样会回收已消亡的子进程的剩余资源。

    4、孤儿进程

    • (1)父进程先于子进程结束,此时子进程成为一个孤儿进程。
    • (2)Linux 系统规定:所有孤儿进程都成为一个特殊进程(进程 1,也就是 init 进程)的子 进程。

    七、父进程 wait(系统调用)回收子进程

    1.wait 工作原理

    • (1)子进程结束时,系统向其父进程发送 SIGCHILD 信号,父进程被唤醒后去回收僵尸子进程资源。
    • (2)父进程调用 wait 后就进入阻塞状态,直到进程资源回收完成。但若父进程没有任何子进程则 wait 返回错误。
    • (3)父子进程之间是异步的,SIGCHILD 信号机制就是为了解决异步的父子进程之间的通信问题。

    2、wait 实战编程

    • (1)wait 原型 pid_t wait(int wstatus),参数wstatus 用来返回子进程结束时的状态,父进程得到 wait 后再结合一些宏定义就可以直到关于子进程结束时的一些信息。
    • (2)**wait 的返回值 pid_t 就是本次回收的子进程的 PID。**即父进程 wait 回收子进程后可以得到子进程的 PID 以及子进程结束时的状态。
    • (3)WIFEXITED、WIFSIGNALED、WEXITSTATUS 这几个宏定义来获取子进程状态。
      • ①WIFEXITED:用来判断子进程是否正常终止(return、exit、_exit),是则返回 1、否则返回 0。
      • ②WIFSIGNALED:用来判断子进程是否非正常终止(被信号所终止),是则返回 1、否则返回 0。
      • ③WEIXTSTATUS:用来得到正常终止情况下子进程的返回值。
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(void)
    {
    	pid_t pid=-1;
    	pid_t ret=-1;
    	int status=-1;
    	pid=fork();
    	if(pid>0){//parent====对僵尸子进程解放其栈与struct结构体资源
    		printf("parent.\n");
    		ret=wait(&status);//status表示子进程返回子进程终止的状态,ret即为回收对象子进程的pid号
    		printf("子进程已经回收,子进程pid=%d.\n",ret);
    		printf("子进程是否正常退出:%d\n",WIFEXITED(status));
    		printf("子进程是否非正常退出:%d\n"WIFSIGNALED(status));
    		pritnf("正常终止的终止值是:%d.\n",WEXITSTATUS(status));
    	}
    	else if(pid==0){//child
    		printf("child pid = %d.\n",getpid());
    		return 51;//异常退出
    		//exit(0);//正常退出
    	}
    	else return -1;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

    八、waitpid 系统调用介绍

    1.wait 和 waitpid 差别

    • (1)基本功能一样,都是用来回收子进程。
    • (2)waitpid 可以回收指定 PID 的子进程,可以选择阻塞式或非阻塞式两种模式,而wait不能指定且是阻塞的,直到进程回收完资源。

    2.waitpid 原型介绍

    • (1)pid_t waitpid(pid_t pid, int *wstatus, int options),返回的 pit_t 为回收的子进程的 PID。
    • (2)输入的 pid_t 为指定的要回收的子进程的 PID,若为-1 则不指定 PID。
    • (3)输入的*wstatus 用于接收子进程状态,options 则为阻塞或非阻塞选项,0 表示默认的 阻塞方式,WNOHANG 则表示非阻塞式。
    • waitpid(-1,&status,0); 等价于 wait(&status);

    3、代码示例

    • (1)使用 waitpid 实现 wait 效果:ret = waitpid(-1, &status, 0);
    • (2)指定 pid、阻塞式回收:ret = waitpid(pid, &status, 0);
    • (3)指定 pid、非阻塞式回收:ret = waitpid(pid, &sstatus, WNOHANG);

    4、竞态初步引入

    • (1)竞态即竞争状态,多进程环境下,多个进程同时抢占系统资源(内存、CPU、文件 IO)。
    • (2)竞争状态对 OS 来说是很危险的,如果没有处理好就会造成结果不稳定。
    • (3)操作系统提供了一系列消灭竞态的机制,我们需要在合适的地方使用合适的方式来消 灭竞态。

    九、exec 族函数及实战

    1、为什么需要 exec 族函数

    • (1)fork 系统调用是为了执行新的程序,但需要直接在子进程的 if 里写入新的程序代码, 这样不够灵活,譬如说我们如果想要执行 ls -al 就不可以了(没有源代码,只有可执 行程序)
    • (2)使用 exec 族函数可以运行新的可执行程序,即把一个编译好的可执行程序直接加载运 行。
    • (3)我们有了exec族函数后,我们典型的父子进程就是这样的:子进程需要运行的程序被单独编写、单独编译成一个可执行文件(叫hello),项目是一个多进程项目,主程序叫父进程,fork创建了一个子进程后在进程中exec来执行hello,达到父子进程分别作不同的程序(宏观上)同时运行。

    2、exec 族的 6 个函数介绍

    /*execl和execv:这两个是最基本的exec,都可以用来加载运行可执行程序,
    *区别是传参格式不同。
    *execl是多个参数排列的形式。
    *execv多多参数事先放在一个字符串数组NULL结尾,再把这数组作为形参传入*/
    int execl(const char* path,const char* arg,/*...,(char*)NULL*/);
    int execv(const char* path,const char* argv[]);
    
    /*execlp和execvp:这两个函数较上面来说,上面两个函数必须指定可执行程序的全路径,而这两个加了p的函数可以只指定file即文件名,函数会到环境变量PATH所指定的目录下去找。
    */
    int execlp(const char* file,const char* arg,/*...,(char*)NULL*/);
    int execvp(const char* path,char* const argv[]);
    
    /*这两个函数的形参列表多了个字符串数组envp,即环境变量*/
    int execle(const char* file,const char* arg,/*...,(char*)NULL,char* const envp[]*/);
    int execvpe(const char* file,char* const argv[],char* const envp[]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3、exec实战1

    • (1)使用 execl 运行 ls -l -a
      execl("/bin/ls","ls","-l","-a",NULL);
    • (2)使用 execv 运行 ls -l -a
      char* const arg[]={"ls","-l","-a",NULL};
      execv("/bin/ls",arg);
    • (3)使用 execl 运行自己写的程序
      execl("hello","aaa","bbb",NULL);
    • (4)使用 execv 运行自己写的程序
      char * const arg[] = {"aaa", "bbb", NULL};
      execv("hello",arg);

    4、exec 实战 2

    • (1)execlp
      execlp("ls ","ls","-l","-a",NULL);
    • (2)execle
      char* const envp[]={"AA=aaaa","XX=abcd",NULL};
      execle("hello","hello","-l","-a",NULL,envp);

    5、execle和execvpe

    • (1)main 函数的原型其实不止是 int main(int argc, char **argv),还可以是 int main(int argc, **argv, char **env),第三个参数是一个字符串数组,内容是环境变量。
    • (2)如果用户在执行 execle 和 execvpe 的时候没有传递第三个参数,则程序会默认从父进 程那里继承一份环境变量(默认的,最早来源于 OS);如果传递了第三个参数则替代 了默认的环境变量。

    十、进程状态和 system 函数

    1、进程的 5 种状态

    • (1)就绪态:所有运行条件已就绪,只要得到了 CPU 时间就可运行
    • (2)运行态:得到 CPU 时间正在运行。
    • (3)僵尸态:进程已经结束了但父进程还没来得及回收。
    • (4)**等待态:包括浅度睡眠跟深度睡眠。**进程在等待某种条件,条件成熟后即进入就绪态。 浅度睡眠时进程可以被信号唤醒,但深度睡眠时必须等到条件成熟后才能结束睡眠状态。
    • (5)暂停态:暂时停止参与 CPU 调度(即使条件成熟),可以恢复。

    2、进程各种状态之间转换图
    在这里插入图片描述
    3、system 函数简介

    • (1)system 函数 = fork + exec 系列函数,其原型为 int system(const char * string)
    • (2)system 函数是原子操作,即整个过程一旦开始就不能被打断(没有竞争状态),直到 执行完毕。但若时间过长可能会影响操作系统整体的实时性。
    • (3)使用 system 调用 ls
      system("ls -al /etc/passwd/etc/shadow");

    十一、进程关系

    1.无关系

    2.父子关系

    3.进程组(group):由若干进程构成一个进程组,组内共享一些文件。

    4.会话(session):会话就是进程组的组。

    十二、守护进程的引入

    1、进程查看命令 ps

    • (1)ps -ajx:偏向于显示与进程有关的各种 ID 号
      在这里插入图片描述
    • (2)ps -aux:偏向于显示进程占用的各种资源
      在这里插入图片描述

    2、向进程发送信号指令:kill

    • (1)kill -信号编号 进程 ID:向特定进程发送一个特定信号。
    • (2)kill -9 XXX:向 XXX 这个进程发送 9 号信号,即结束进程。

    3.守护进程

    • (1)守护进程:用 daemon 表示守护进程,简称为 d,进程名后面带 d 的基本就是守护进程。
    • (2)守护进程的特征:
      • 长期运行:一般是开机运行到关机。
      • 与控制台脱离:普通进程与运行该进程的控制台绑定,表现为如果终端被关闭则这个终端 中运行的所有进程都会被关闭。守护进程则不是。
      • 服务器一般都为守护进程:服务器程序就是一个一直运行的程序,可以为我们提供某种服务。

    4、常见守护进程

    • (1)syslogd:系统日志守护进程,提供 syslog 功能。
    • (2)cron:用来实现操作系统的时间管理,Linux 中实现定时功能就需要用到 cron。

    十三、编写简单守护进程

    1.任何一个进程都可以将自己变成守护进程

    2.create_deamon()函数要素(调用该函数即可成为守护进程):

    • (1)子进程等待父进程退出,即将当前进程变为子进程。==变孤儿进程
    • (2)子进程使用 setsid 创建新的会话,脱离控制台。==脱离控制台
    • (3)调用 chdir 将当前工作目录设置为“/”。==自成一派
    • (4)umask 设置为 0 以取消任何文件权限屏蔽。
    • (5)关闭所有文件描述符。
    • (6)将文件描述符 0、1、2 定位到“/dev/null”
    #include 
    #include 
    #include 
    #include 
    #include 
    void create_daemon(void);
    int main(void)
    {
    	create_daemon();
    	while(1){
    		printf("i am running.\n");sleep(1);
    	}
    	return 0;
    }
    // 函数作用就是把调用该函数的进程变成一个守护进程
    void create_daemon(void)
    {
    	pid_t pid=0;
    	pid=fork();
    	if(pid<0){
    		perror("fork");exit(-1);
    	}
    	else if(pid>0){//parent process
    		exit(0);
    	}
    	//===此处就是孤儿进程
    	//设置新的会话session,脱离控制台
    	pid=setsid();
    	if(pid<0){
    		perror("setsid");exit(-1);
    	}	
    	//将当前进程的目录设置为根目录
    	chdir("/");
    	//umask设置为0确保之后进程有最大的文件操作权限
    	umask(0);
    	//关闭all fd,前提要知道当前文件系统所允许打开最大文件描述符数目
    	int cnt=sysconf(_SC_OPEN_MAX);int i=0;
    	for(i=0;i<cnt;i++)
    		close(fd);
    	//将文件描述符0、1、2定位到“/dev/null”
    	open("/dev/null",O_RDWR);
    	open("/dev/null",O_RDWR);
    	open("/dev/null",O_RDWR);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    十四、使用 syslog 来记录调试信息

    1、三个函数:

    • void openlog(const char* ident,int option,int facility);
    • void syslog(int priority,const char* format,...);
    • void closelog(void);

    2、syslog工作原理

    • (1)一般 log 信息都存储在/var/log/messages 这个文件中,但 Ubuntu 的 log 信息存储在 /var/log/syslog。
    • (2)操作系统中有一个守护进程 syslogd,它其实就是一个服务器进程,负责操作系统日志文件的写入和维护。
    • (3)syslogd 是独立于任何一个进程的,我们当前进程可以通过 openlog 打开一个和 syslogd 的连接通道,然后通过 syslog 向 syslogd 发消息,然后由 syslogd 将其写入日志文件系 统中
    #include 
    #include 
    #include 
    int main(void)
    {
    	printf("my pid=%d.\n",getpid());
    	openlog(LOG_INFO,"this is my log info.%d",23);
    	syslog(LOG_INFO,"this is another log info.");
    	syslog(LOG_INFO,"this is 3th log info.");
    	closelog();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    十五、让程序单例运行

    1.问题

    • (1)守护进程是长时间运行而不退出的,因此执行一次就有一个进程,执行多次就有多个 进程。但这并不是我们想要的,因为守护进程一般都是服务器程序,服务器程序只需 要运行一个就够了,多个相同的服务器同时运行没有意义甚至会带来错误。
    • (2)因此我们希望程序有一个单例运行的功能,也就是说当我们去运行一个程序的时候, 如果当前还没有这个程序对应的进程则运行,如果有则提示错误。

    2.解决方法

    • (1)用一个文件的存在与否来表示是否有当前程序对应的进程在运行。具体做法是:在程 序执行之初去判断一个特定的文件是否存在,若存在则表明程序已经在运行,这时应 该退出程序;若不存在则表明程序没有在运行,那么就需要创建这个文件,并且在程序结束之后删除这个文件。
    • (2)这个特定文件要古怪一点,确保不会有同名文件存在。
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define FILE "/var/aston_test"
    void delete_file(void);
    int main(void)
    {
    	//首先就是要判断文件是否存在
    	int fd=-1;
    	fd=open(FILE,O_RDWR|O_TRUNC|O_CREAT|O_EXCL,0664);
    	if(fd<0){
    		if(errno==EEXIST){
    			printf("进程已经存在,不需要重复执行.\n");return -1;
    		}
    	}
    	atexit(delete_file);//注册进程处理函数
    	int i=0;
    	for(i=0;i<10;i++){
    		printf("i am running...%d\n",i);sleep(1);
    	}
    	return 0;
    }
    void delete_file(void)
    {
    	remove(FILE);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    十六、Linux 的进程间通信概述

    1.为什么需要进程间通信

    • (1)进程间通信(IPC)是指任意两个进程之间的通信。
    • (2)同一进程在一个地址空间中,所以同一个进程的不同模块(不同函数、不同文件)之间的通信是很简单的,如全局变量、函数参数。==线程间通信?
    • (3)两个不同的进程处于不同的地址空间,所以互相通信很难。

    2.什么样的程序设计需要进程间通信。

    • (1)绝大部分程序都是不需要进程间通信的,因为它们是单进程的。
    • (2)复杂、大型的程序才需要进程间通信,如 GUI、服务器。

    3.Linux 内核提供的多种进程间通信方式

    • (1)无名管道和有名管道:本质是一块内存,面向字节流、生命周期随内核、半双工、自带同步互斥机制。
    • (2)SystemV IPC:
      • 信号量:是一个计数器,作为进程间以及线程间的同步手段
      • ②消息队列:可认为是全局链表,允许一个或多个进程读写,生命周期随内核。
      • ③共享内存:将同一块物理内存映射到不同进程的虚拟地址空间中,实现资源共享。是最快 的 IPC 方式。
    • (3)socket 域套接字:用于不同机器的进程间通信。
    • (4)信号:比较复杂的通信方式,用于通知接收进程某个事件已经发生。
    • (5)文件锁:与信号量类似,用于进程间对同一文件的互斥访问。

    十七、Linux 的 IPC 机制——管道,内核维护的一块内存

    1、管道(无名管道)

    • (1)原理:内核维护的一块内存,有读端和写端。
    • (2)方法:父进程创建管道后后 fork 子进程,子进程继承父进程的管道 fd。
    • (3)限制:只能在父子进程间通信、半双工。
    • (4)函数:pipe、write、read、close。

    2、有名管道(fifo)

    • (1)原理:实质也是内核维护的一块内存,表现为一个有名字的文件。
    • (2)方法:固定一个文件名,两个进程分别使用 mkfifo 创建 fifo 文件,然后分别 open 打开获取 fd,然后一个读一个写。
    • (3)限制:半双工。
    • (4)函数:mkfifo、open、write、read、close

    十八、SystemV IPC 介绍

    1、基本特点

    • (1)系统通过一些专用 API 来提供 SystemV IPC 功能。实质也是内核提供公共内存。
    • (2)种类:信号量、消息队列、共享内存。

    2、信号量

    • (1)实质就是个计数器(可理解为 int a)。
    • (2)通过计数值来提供互斥与同步

    3、消息队列

    • (1)本质上是一个队列,队列可以理解为内核维护的一个 FIFO。
    • (2)工作时 A 和 B 两个进程进行通信,A 向队列中放入消息,B 从队列中读出消息。

    4.共享内存

    • (1)大片内存直接映射。
    • (2)类似于 LCD 显示时的显存用法

    剩余的 2 类 IPC

    • (1)信号。
    • (2)Unix 域套接字 socket
  • 相关阅读:
    ROS-读取/map话题转化为pgm文件(代码版map_server)
    腾讯后端开发高频问题和答案
    Obsidian -知识库管理工具使用总结
    【毕业设计】深度学习手势识别系统 - yolo python opencv 机器视觉
    ChatGPT是如何产生心智的?
    AbstractControllerUrlHandlerMapping类简介说明
    Unity微信小游戏无法调起输入框
    简单的分布式锁+队列
    想要精通算法和SQL的成长之路 - 存在重复元素
    Springboot 集成 Ehcache操作数据库显示SQL语句设置
  • 原文地址:https://blog.csdn.net/weixin_47397155/article/details/126120061