版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、ARM 应用系统设计第7章 Linux编程简介,佘黎煌 东北大学信息科学与工程学院 电子信息工程研究所,现在 Linux 广泛用于各类计算应用,不仅包括 IBM 的微型 Linux 腕表、手持设备(PDA 和蜂窝电话)、因特网装置、瘦客户机、防火墙、工业机器人和电话基础设施设备,甚至还包括了基于集群的超级计算机。,嵌入式Linux概况,Linux操作系统开放源代码,可以裁剪内核,并已在x86、Alpha、Sparc、MIPS、PPC、Motorola、NEC和ARM等硬件平台上稳定、高效地运行。 Linux操作系统作为一种多任务、稳定可靠、内核可裁剪的系统,是开发嵌入式软硬件产品的优秀软件平台
2、。 嵌入式Linux是一种开放源码、软实时、多任务的嵌入式操作系统。,嵌入式Linux概况,Linux入门需要掌握的内容,Linux 环境的使用 会使用VI编辑器 掌握一些基本的命令 Linux编程需要掌握的内容 GNU GCC编译工具 进程 文件操作 信号处理 消息管理 线程操作 网络编程,Linux 操作系统界面,在Linux系统中打开终端的方式有以下两种: 种是在桌面上依次单击“主程序系统工具终端”可打开如图2-1的终端窗口;另一种是在Linux桌面上单击鼠标右键,从弹出的快捷菜单中选择“终端”命令,也可打开终端窗口。,Linux入门需要掌握的内容,GNU GCC编译工具 进程 文件操作
3、 信号处理 消息管理 线程操作 网络编程,Linux 下C代码的编译,开发工具GNU 的介绍 GNU 软件包括C 编译器GCC,C+编译器G+,汇编器AS,链接器LD,二进制转换工具(OBJCOPY,OBJDUMP),调试工具(GDB,GDBSERVER,KGDB)和基于不同硬件平台的开发库。 在GNU GCC 支持下用户可以使用流行的C/C+语言开发应用程序,满足生成高效率运行代码的需求,Linux 下C代码的编译,/* * File Name: hello.c * Description:introduce how to compile a source file with gcc * #
4、include void main() printf(Hello worldn); $ gcc -o hello hello.c,Linux 下C代码的编译,GCC 是一个多目标的工具。GCC 最基本的用法是: gcc options file. , 其中的option 是以“-”开始的各种选项,file 是相关的文件名。在使用GCC 的时候,必须要给 出必要的选项和文件名。GCC 的整个编译过程,实质上是分4 步进行的,每一步完成一个特定的工作,这4 步分别是:预处理、编译、汇编和链接。它具体完成哪一步,是由GCC 后面的开关选项和文件类型决定的。,Linux 下C代码的编译,GCC 编译器
5、有许多选项,但对于普通用户来说只要知道其中常用的几个就够了。在这里列出几个最常用的选项。 -o 选项表示要求编译器生成指定文件名的可执行文件。 -c 选项表示只要求编译器进行编译,而不要进行链接,生成以源文件的文件名命名但把其后缀由.c 或.cc 变成.o 的目标文件。 -g 选项要求编译器在编译的时候提供以后对程序进行调试的信息。 -E 选项表示编译器对源文件只进行预处理就停止,而不做编译、汇编和链接。 -S 选项表示编译器只进行编译,而不做汇编和链接。 -O 选项是编译器对程序提供的编译优化选项,在编译的时候使用该选项,可以使生成的执行文件的执行效率提高。 -Wall 选项指定产生全部的警
6、告信息。 gcc -Wall -O -g -c main.c -o main.o,Linux 下C代码的编译,GNU Make 是负责从项目的源代码中生成最终可执行文件和其他非源代码文件的工具。 $ make -f makefilename makefile 的例子。 executable : main.o io.o gcc main.o io.o -o executable main.o : main.c gcc -Wall -O -g -c main.c -o main.o io.o : io.c gcc -Wall -O -g -c io.c -o io.o 第一行称之为规则,第二行是执
7、行规则的命令,必须要以tab 键开始,Linux 下C代码的编译,executable : main.o io.o gcc main.o io.o -o executable main.o : main.c gcc -Wall -O -g -c main.c -o main.o io.o : io.c gcc -Wall -O -g -c io.c -o io.o 1.executable 是makefile 最终要生成的目标文件。给出的规则说明executable 依赖于两个目标文件main.o 和io.o,只要executable 比它依赖的文件中的任何一个旧的话,下一行的命令就会被执行。
8、 2.但是,在检查文件 main.o 和io.o的日期之前,它会往下查找那些把main.o 或io.o 做为目标文件的规则。Make 先找到了关于 main.o的规则,该目标文件的依赖文件是main.c。 3.makefile 后面的文件中再也找不到生成这个依赖文件的规则了。此时,Make 开始检查磁盘上这个依赖文件的日期,如果这个文件的日期比main.o 日期新的话,那么这个规则下面的命令gcc -Wall -c main.c -o main.o 就会执行,,Linux 下C代码的编译,Makefile 宏定义 OBJS = main.o io.o CC = gcc CFLAGS = -Wa
9、ll -O -g executable: $(OBJS) $(CC) $(OBJS) -o executable main.o : main.c $(CC) $(CFLAGS) -c main.c -o main.o io.o : io.c $(CC) $(CFLAGS) -c io.c -o io.o 在这个makefile 中引入了三个宏定义,所以如果这些宏中的某些值发生变化,开发者只需在要修改的宏处,将其宏值修改为要求的值即可,。在make中还有一些已经定义好的内部变量,几个较常用的变量是$、$、$?、$*和$。 $ 扩展为当前规则的目标文件名。 $ 扩展为当前规则依赖文件列表中的第一个
10、依赖文件。 $? 扩展为所有的修改日期比当前规则的目标文件的创建日期更晚的依赖文件,该值只有在使用显式规则时才会被使用。 $* 扩展成当前规则中目标文件和依赖文件共享的文件名,不含扩展名。 $ 扩展为整个依赖文件的列表(除去了所有重复的文件名)。,OBJS = main.o io.o CC = gcc CFLAGS = -Wall -O -g executable: $(OBJS) $(CC) $ -o $ main.o : main.c $(CC) $(CFLAGS) c $ -o $ io.o : io.c $(CC) $(CFLAGS) -c $ -o $,Makefile示例,编写该程
11、序的编译脚本Makefile,Makefile示例,文件名Makefile: main:main.o mytool1.o mytool2.o gcc o main main.o mytool1.o mytool2.o main.o:main.c mytool1.h mytool2.h gcc c main.c mytool1.o:mytool1.c mytool1.h gcc c mytool1.c mytool2.o:mytool2.c mytool2.h gcc c mytool2.c,Makefile示例,main:main.o mytool1.o mytool2.o gcc o $ $
12、 main.o:main.c mytool1.h mytool2.h gcc c $ mytool1.o:mytool1.c mytool1.h gcc c $ mytool2.o:mytool2.c mytool2.h gcc c $,Linux入门需要掌握的内容,GNU GCC编译工具 进程 文件操作 信号处理 消息管理 线程操作 网络编程,进程,通俗的讲程序是一个包含可以执行代码的文件,是一个静态的文件,而进程是一个开始执行但没有结束的程序的实例,就是可执行文件的具体实现,一个程序可能有许多进程,而一个进程又可以有许多线程,依次循环下去,而产生子孙线程. 为了区分各个不同的进程,系统给每
13、一个进程分配了一个ID(就象我们的身份证)以便识别 每个进程分为新建,运行,阻塞,就绪和完成五个状态,进程,进程都有一个ID,系统可通过调用getpid得到进程的ID,而getppid可以得到父进程的ID #include pid_t getpid(void); pid_t getppid(void);,进程的创建,1.使用fork()函数,就象进程克隆(clone)自己一样,创建两个一模一样的进程是没有,通过fork的返回值来区分父进程和子进程,错误返回-1,对于父进程返回子进程的ID,对于子进程返回0. voidmain()inti;if(child=fork()=-1) printf(“
14、Fork errorn”); exit(1); else if(fork()=0) /*子进程程序*/ for(i=1;i5;i+) printf(Thisischildprocessn); exit(1) else /*父进程程序*/ for(i=1;i5;i+) printf(Thisisprocessprocessn); ,进程的创建,2进程如何来启动另一个程序的执行。在Linux中要使用exec类的函数,exec类的函数不止一个,但大致相同,在Linux中,它们分别是:execl,execlp,execle,execv,execve和execvp. voidmain()intrtn;/
15、*子进程的返回数值*/while(1)/*从终端读取要执行的命令*/printf();fgets(command,256,stdin);commandstrlen(command)-1=0;if(fork()=0)/*子进程执行此命令*/execlp(command,command);/*如果exec函数返回,表明没有正常执行命令,打印错误信息*/perror(command);exit(errorno); else/*父进程,等待子进程结束,并打印子进程的返回值*/wait(,Linux入门需要掌握的内容,GNU GCC编译工具 进程 文件操作 信号处理 消息管理 线程操作 网络编程,Lin
16、ux下文件操作,文件I/O函数-打开文件、读文件、写文件等等。大多数UNIX文件I/O只需用到5个函数:open、read、write、lseek 以及close。 open函数 #include #include #include int open(const char * pathname, int oflag,. /*, mode_t mode * / ) ;,Linux下文件操作,pathname是要打开或创建的文件的名字。of lag参数可用来说明此函数的多个选择项。用下列一个或多个常数进行或运算构成of lag参数(这些常数定义在头文件中): O_RDONLY 只读打开。 O_WR
17、ONLY 只写打开。 O_RDWR 读、写打开。 在这三个常数中应当只指定一个。下列常数则是可选择的: O_APPEND 每次写时都加到文件的尾端。 O_CREAT 若此文件不存在则创建它。 O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。这可测试一个文件是否存在,如果不存在则创建此文件成为一个原子操作。 O_TRUNC 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。,int creat(const char *pathname,mode_t mode); 以mode方式创建一个以pathname为文件名的文件,返回新的文件句柄fd,错误返回-1及错误代码e
18、rrno。,size_t read(int fd,void *buf, size_t count); 把fd指向的文件传送count字节到buf指针所指向的内存中,正确返回实际写入的字节数,错误返回-1及错误代码errno。,Linux下文件操作,size_t write(int fd,void *buf, size_t count); 把buf指针指向的内存count字节传送到fd指向的文件中,正确返回读到的字节数或0,错误返回-1及代码errno。,off_t lseek(int fd,off_t offset,int where); 将fd所指文件的读写指针在where位置移动offse
19、t个位移量 int close(int fd); 关闭fd所指文件,顺利关闭返回0,错误返回-1。 程序框图。,Linux下文件操作,Linux下文件操作,#include #include #include #include int main() char c; int in, out; in = open(“file.in”, O_RDONLY); out = open(“file.out”, O_WRONLY|O_CREAT); while(read(in, ,Linux入门需要掌握的内容,GNU GCC编译工具 进程 文件操作 信号处理 消息管理 线程操作 网络编程,捕捉SIGUSR1
20、和SIGUSR2处理程序,信号是软件中断。很多比较重要的应用程序都需处理信号。信号提供了一种处理异步事件的方法 首先,每个信号都有一个名字。这些名字都以三个字符SIG开头。例如,SIGABRT是夭折信号,当进程调用abort函数时产生这种信号。SIGALRM是闹钟信号,当由a l a r m函数设置的时间已经超过后产生此信号。 在头文件中,这些信号都被定义为正整数(信号编号),Linux 系统信号,捕捉SIGUSR1和SIGUSR2处理程序,信号机制最简单的界面是signal函数。 #include void (*signal (int signo, void (*func)(int) (in
21、t); 当指定函数地址时,我们称此为捕捉此信号。我 们称此函数为信号处理程序(signal handler)或信号捕捉函数(signal-catching function),捕捉SIGUSR1和SIGUSR2处理程序,$ a.out ftok函数是根据pathname和proj来创建一个关键字. intmsgget(key_tkey,intmsgflg); intmsgsnd(intmsgid,structmsgbuf*msgp,intmsgsz,intmsgflg); intmsgrcv(intmsgid,structmsgbuf*msgp,intmsgsz,longmsgtype,int
22、msgflg); structmsgbuflongmsgtype;/*消息类型*/./*其他数据类型*/,创建消息队列,发送消息,接收消息,消息数据结构,进程1,服务器进程,#defineMSG_FILEserver.c#defineBUFFER255#definePERMS_IRUSR|S_IWUSRstructmsgtypelongmtype;charbufferBUFFER+1;,进程1,服务器进程,创建消息队列 intmain() structmsgtypemsg;key_tkey;intmsgid;if(key=ftok(MSG_FILE,a)=-1)fprintf(stderr,C
23、reatKeyError:%san,strerror(errno);exit(1);if(msgid=msgget(key,PERM|IPC_CREAT|IPC_EXCL)=-1)fprintf(stderr,CreatMessageError:%san,strerror(errno);exit(1);,进程1,服务器进程,接收和发送消息 while(1)msgrcv(msgid,进程2,客户端进程,#defineMSG_FILEserver.c#defineBUFFER255#definePERMS_IRUSR|S_IWUSRstructmsgtypelongmtype;charbuffer
24、BUFFER+1;,进程2,客户端进程,获取服务器创建的消息 intmain(intargc,char*argv)structmsgtypemsg;key_tkey;intmsgid;if(argc!=2)fprintf(stderr,Usage:%sstringna,argv0);exit(1);if(key=ftok(MSG_FILE,a)=-1)fprintf(stderr,CreatKeyError:%san,strerror(errno);exit(1);if(msgid=msgget(key,PERM)=-1)fprintf(stderr,CreatMessageError:%sa
25、n,strerror(errno);exit(1);,进程2,客户端进程,发送和接收消息 msg.mtype=1;strncpy(msg.buffer,argv1,BUFFER);msgsnd(msgid,Linux入门需要掌握的内容,GNU GCC编译工具 进程 文件操作 信号处理 消息管理 线程操作 网络编程,Linux入门需要掌握的内容,GNU GCC编译工具 进程 文件操作 信号处理 消息管理 线程操作 网络编程,“节俭”的多任务操作方式,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段。 运行一个进程中的多个线程,彼此之
26、间使用相同的地址空间、共享大部分数据、启动一个线程所花费的空间远远小于启动一个进程所花费的空间。线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。,线程特点,对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式费时而不方便。 由于同一进程下的线程之间共享数据空间,它们共享全局变量、共享进程指令、大多数数据和打开的文件(如描述字)、信号处理程序和信号处置、当前工作目录、用户ID和组ID,一个线程的数据可以直接为其他线程所用,快捷方便。,线程间方便的通信机制,线程特点,线程的基本操作函数,先讲述4个基本线程函数,在调用它们前均要包括pthread.h头文
27、件 1.创建线程函数 2.等待线程的结束函数 3.取自己线程ID函数 4.终止线程函数,创建线程函数,int pthread_create(pthread_t *tid,const pthread_attr_t *attr, void *(*func)(void *),void *arg);,线程的基本操作函数,注意: 第一个参数为指向线程标识符的指针 第二个参数用来设置线程属性 第三个参数是线程运行函数的起始地址 最后一个参数是运行函数的参数,等待线程的结束函数,Int pthread_join(pthread_ttid,void*status); 第一个参数为被等待的线程标识符 第二个参数
28、为一个用户定义的指针,它可以用来存储被等待线程的返回值,线程的基本操作函数,这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。,取自己线程ID函数,pthread_self(void); 线程都有一个ID在给定的进程内标识自己。 线程ID由pthread_creat返回,可 以取得自己的线程ID。,线程的基本操作函数,一个线程的结束有两种途径,一种是函数结束,调用它的线程也就结束,另一种方式是通过函数pthread_exit 来实现。 void pthread_exit(void *status); 唯一的参数是函数的返回代码,注
29、:一个线程不能被多个线程等待,否 则第一个接收到信号的线程成功返回,其余调用pthread_join 的线程则返回错误代码ESRCH。,线程的基本操作函数,终止线程函数,简单的多线程编程,Linux系统下的多线程遵循POSIX线程接口,称为pthread 编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a Linux下pthread 的实现是通过系统调用clone()来实现的。,简介,举例,#include #include void thread(void) int i; for(i=0;i3;i+) printf(This is a p
30、thread.n); ,简单的多线程编程,int main(void) pthread_t id; int i,ret; ret=pthread_create(i3;i+) printf(This is the main process.n); pthread_join(id,NULL); return(0); ,简单的多线程编程,运行程序,得到如下结果: This is the main process。 This is a pthread。 This is the main process。 This is the main process This is a pthread This i
31、s a pthread。,简单的多线程编程,再次运行,可能得到如下结果: This is a pthread This is the main process This is a pthread This is the main process This is a pthread This is the main process,简单的多线程编程,前后两次结果不一样,这是两个线程争夺CPU资源的最终结果。,Linux入门需要掌握的内容,GNU GCC编译工具 进程 文件操作 信号处理 消息管理 线程操作 网络编程,网络通讯,socket,struct sockaddr(网络接口),struct
32、 sockaddr_in short int sin_family; /* Internet地址族 */ /*sa_family 一般来说,都是 “AF_INET”*/ unsigned short int sin_port; /* 端口号 */ struct in_addr sin_addr; /* Internet地址 */ unsigned char sin_zero8; /* 添0(和struct sockaddr一样大小)*/ ,bind,int bind (int sockfd , struct sockaddr *my_addr , int addrlen) ; 参数说明: so
33、ckfd 是由socket()函数返回的套接字描述符。 my_addr 是一个指向struct sockaddr 的指针,包含有关你的地址的信息:名称、端口和IP 地址。 addrlen 可以设置为sizeof(struct sockaddr)。 my_addr.sin_family = AF_INET ; /* 主机字节顺序 */ my_addr.sin_port = htons(MYPORT); /* 网络字节顺序,短整型 */ my_addr.sin_addr.s_addr = inet_addr(“166.111.69.52”) ; bzero(,connect,int connect
34、 (int sockfd, struct sockaddr *serv_addr, int addrlen); connect()的三个参数意义如下: sockfd :套接字文件描述符,由socket()函数返回的。 lserv_addr 是一个存储远程计算机的IP 地址和端口信息的结构。 addrlen 应该是sizeof(struct sockaddr)。 dest_addr.sin_family = AF_INET ; /* 网络字节顺序,短整型 */ dest_addr.sin_port = htons (DEST_PORT); dest_addr.sin_addr.s_addr =
35、inet_addr(DEST_IP); /* 将剩下的结构中的空间置0 */ bzero(,listen,int listen(int sockfd, int backlog); listen()函数的参数意义如下: sockfd 是一个套接字描述符,由socket()系统调用获得。 backlog 是未经过处理的连接请求队列可以容纳的最大数目,accept,int accept(int sockfd, void *addr, int *addrlen); accept()函数的参数意义如下: sockfd 是正在listen() 的一个套接字描述符。 addr 一般是一个指向struct s
36、ockaddr_in 结构的指针;里面存储着远程连接过来的计算机的信息(比如远程计算机的IP 地址和端口)。 addrlen 是一个本地的整型数值,在它的地址传给accept() 前它的值应该是sizeof(struct sockaddr_in);,send,int send(int sockfd, const void *msg, int len, int flags); send 的参数含义如下: sockfd 是代表你与远程程序连接的套接字描述符。 msg 是一个指针,指向你想发送的信息的地址。 len 是你想发送信息的长度。 flags 发送标记。一般都设为0,recv,int rec
37、v(int sockfd, void *buf, int len, unsigned int flags); recv()的参数含义如下: sockfd 是你要读取数据的套接字描述符。 buf 是一个指针,指向你能存储数据的内存缓存区域。 len 是缓存区的最大尺寸。 flags 是recv() 函数的一个标志,一般都为0,服务器端,这个服务器所有的工作就是给远程的终端发送一个字符串:“Hello,World!”你所需要做的就是在命令行上启动这个服务器,然后在另外一台机器上使用telnet 连接到这台我们自己写的服务器上: $ telnet remotehostname 4000 remote
38、hostname 就是你运行我们自己写的服务器的那台机器名。 int main(int argc, char *argv),服务器端,在面向连接的协议的程序中,服务器执行以下函数: 调用socket()函数创建一个套接字。 调用bind()函数把自己绑定在一个地址上。 调用listen()函数侦听连接。 调用accept()函数接受所有引入的请求。 调用recv()函数获取引入的信息然后调用send()回答。,服务器端,#include #include #include #include #include #include #include #include /* 服务器要监听的本地端口 *
39、/ #define MYPORT 4000 /* 能够同时接受多少没有accept 的连接 */ #define BACKLOG 10,定义服务器的断口,和最大连接数,服务器端,创建套接字,main() /* 在sock_fd 上进行监听,new_fd 接受新的连接 */ int sock_fd, new_fd ; /* 自己的地址信息 */ struct sockaddr_in my_addr; /* 连接者的地址信息*/ struct sockaddr_in their_addr; int sin_size; if (sockfd =socket(AF_INET, SOCK_STREAM,
40、 0) = -1) /* 输出错误提示并退出 */ perror(“socket”); exit(1); ,创建套接字并定义为TCP协议,服务器端,绑定套接字,/* 主机字节顺序 */ my_addr.sin_family = AF_INET; /* 网络字节顺序,短整型 */ my_addr.sin_port = htons(MYPORT); /* 将运行程序机器的IP 填充入s_addr */ my_addr.sin_addr.s_addr = INADDR_ANY; /* 将此结构的其余空间清零 */ bzero( ,将套接字和服务器的IP地址,端口绑定,服务器端,监听,/* 这里是我们一直强调的错误检查! */ if (listen(sockfd, BACKLOG) = -1) /* 如果调用listen 失败,则给出错误提示,退出 */ perror(“listen”); exit(1); ,服务器监听,等客户端连接请求,服务器端,while(1) /* 这里是主accept()循环 */ sin_size = sizeof(struct sockaddr_in); if
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 渭滨区2025陕西宝鸡市渭滨区招聘硕士及以上研究生紧缺特殊专业人才28人笔试历年参考题库典型考点附带答案详解
- 2026贵州盐业(集团)毕节有限责任公司招聘人员聘前笔试历年参考题库附带答案详解
- 2026福建厦门金鹭校园招聘129人笔试历年参考题库附带答案详解
- 2026湖南郴州莽山旅游开发有限责任公司招聘岗位笔试历年参考题库附带答案详解
- 2026海南东方俄贤岭景区开发有限公司招聘市场部副部长1人笔试历年参考题库附带答案详解
- 外科急腹症的家庭护理
- 围手术期人文关怀护理
- 小学手工创意2025实践说课稿
- 小学心理教育教案2025年情绪疏导角色说课稿
- 基础护理中的社会支持系统
- 2026版HSK-6级原创训练卷(含答案、详细解析及评分标准)
- 管道施工扬尘防控科学措施
- 2025年机械安全操作规范考试试题集和答案
- 人教版六年级数学下册全册教学设计及教学反思
- 河北廊坊安全员考试试题及答案
- 中国人民革命军事博物馆
- 急诊科气道异物急救护理流程
- 中医护理常规技术操作规程完整
- 超长期特别国债项目申报工作指南
- 2026云南昆明市官渡区国有资产投资经营有限公司招聘5人考试备考试题及答案解析
- 招标档案移交制度
评论
0/150
提交评论