0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

英创信息技术Linux双进程应用示例

英创信息技术 来源:英创信息技术 作者:英创信息技术 2020-02-06 11:34 次阅读

1、概述

一台典型的工控设备通常包括若干通讯接口网络、串口、CAN等),以及若干数字IO、AD通道等。运行于设备核心平台的应用程序通过操作这些接口,实现特定的功能。通常为了高效高精度完成整个通讯控制流程,应用程序采用C/C++语言来编写。图1表现了典型工控设备的组成关系。

典型工控设备框图

工控设备的另一个特点是鉴于设备大多是24小时连续运行,且无人值守,所以基本的工控设备是无显示的。英创的工控主板ESM6800、ESM335x等都大量的应用于这类无头工控设备之中。

在实际应用中,部分客户需要基于已有的无头工控设备,增加显示界面功能,以满足新的应用需求。显然保持已有的基本工控处理程序不变,通过相对独立的技术手段来实现显示功能,最符合客户的利益诉求。为此我们发展了一种双进程的程序设计方案来满足客户的这一需求。该方案的第一个进程,以客户已有的用C/C++写的基础工控进程为基础,仅增加一个面向本地IP(127.0.0.1)的侦听线程,用于向显示进程提供必要的运行工况数据。图2为增添了服务线程的工控进程:

带有侦听线程的基础工控进程

方案的第二个进程则主要用于实现显示界面,可以采用各种手段来实现,本文中介绍了使用Qt的QML语言加通讯插件的界面设计方法。第二个进程(具体是通讯插件单元)通过本地IP,以客户端方式与基础工控进程进行Socket通讯,完成进程间数据交换。显示进程以及与工控进程的关系如图3所示:

显示进程与工控进程

2、系统设计

鉴于工业控制领域对系统运行的稳定性要求,控制系统更加倾向于将底层硬件控制部分与上层界面显示分开,两部分以双进程的形式各自独立运行。底层硬件控制部分将会监控系统硬件,管理外设等,同时收集系统的状态;而上层界面显示部分主要用于显示系统状态,并实现少量的系统控制功能,方便维护人员查看系统运行状态并且根据当前状态进行系统的调整。由于显示界面不一定是所有设备都配置,而且显示部分的程序更加复杂,从而更容易出现程序运行时的错误,将控制与显示分开能够避免由于显示部分的程序问题而影响到整个控制系统的运行,而且没有配置显示屏的设备也可以直接运行底层的控制程序,增加了系统程序的兼容性。显示与控制分离后,由于显示界面程序不需要处理底层硬件的管理控制,在设计时可以更加注重于界面的美化,而且界面程序可以采用不同的编程语言进行开发,比如使用Qt C++或者Android java,本文将介绍基于Linux + Qt的双进程示例程序供客户在实际开发中参考,关于Android程序请参考我们官网的另一篇文章:《Android双应用进程Demo程序设计》。

如上图所示。整个系统分为控制和显示两个进程,底层硬件控制部分可以独立运行,使用多线程管理不同的硬件设备,监控硬件状态,将状态发送给socket服务器,并且从socket服务器接收命令来更改设备状态。Socket服务器也是一个独立的线程,通过本地网络通信集中处理来自硬件控制线程以及显示程序的消息。显示界面需要连接上socket服务器才能正确的显示设备的状态,同时提供必须的人工控制接口,供设备使用过程中人为调整设备运行状态。目前在ESM6802工控主板上,界面程序可以采用Qt C++编写,也可以使用Android java进行开发,本文仅介绍采用Qt的界面程序。显示程序界面用QML搭建,与底层通信的部分用独立的Qt QML插件实现,这样显示部分进一部分离为数据处理和界面开发,使得界面设计可以更加快捷。程序的整体界面效果如下图所示:

目前我们只提供了串口(SERIAL)和GPIO两部分的例程。下面将集中介绍程序中通过本地IP实现两个进程通信的部分供客户在实际开发中参考。

3、控制端C程序

控制端程序主要分为两个部分,一个部分用于控制具体的硬件运行(下文称为控制器),另一个部分为socket服务器,用于与显示程序之间进行通信。由于本方案主要是为了展示在已有控制程序的基础上,增加显示界面功能,以满足新的应用需求,所以我们在此重点介绍在已有控制程序中加入socket服务器的部分,不再详细介绍各硬件的具体控制的实现。

增加本地IP通信的功能,首先需要在控制进程中新加入一个socket服务器线程,用于消息的集中管理,实现底层硬件与上层的界面程序的信息交换,socket服务器线程运行的函数体代码如下:

staticvoid*_init_server(void*param)
{
intserver_sockfd, client_sockfd;
intserver_len;
structsockaddr_inserver_address;
structsockaddr_inclient_address;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr("127.0.0.1");//通过本地ip通信
server_address.sin_port = htons(9733);
server_len =sizeof(server_address);
bind(server_sockfd, (structsockaddr*)&server_address, server_len);
listen(server_sockfd, 5);
intres;
pthread_t client_thread;
pthread_attr_t attr;
charid[4];
client_element*client_t;
while(1)
{
if(!client_has_space(clients))
{
printf("to many client, wait for one to quit...\n");
sleep(2);
continue;
}
printf("server waiting\n");
client_sockfd = accept(server_sockfd, (structsockaddr*)&client_address, (socklen_t *)&server_len);
//get and save client id
read(client_sockfd, &id, 4);
if((id[0]!='I') && (id[1]!='D'))
{
printf("illegal client id, drop it\n");
close(client_sockfd);
continue;
}
client_t = accept_client(clients, id, client_sockfd);
printf("client: %s connected\n", id);
//create a new thread to handle this connection
res = pthread_attr_init(&attr);
if( res!=0 )
{
printf("Create attribute failed\n");
}
//设置线程绑定属性
res = pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
//设置线程分离属性
res += pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED );
if( res!=0 )
{
printf("Setting attribute failed\n");
}
res = pthread_create( &client_thread, &attr,
(void*(*) (void*))socked_thread_func, (void*)client_t );
if( res!=0 )
{
close( client_sockfd );
del_client(clients, client_sockfd);
continue;
}
pthread_attr_destroy( &attr );
}
}

此函数创建一个socket用于监听(listen)等待显示程序连接,当接受(accept)一个连接之后创建一个新的线程用于消息处理,主要用于维护socket连接的状态,解析消息的收发方,并将消息转送到对应的接收方,在显示程序建立连接之前或者连接断开之后,控制器发送的消息将不会进行发送了,而控制器依然在正常运行,用于处理消息的新线程如下:

staticvoid*socked_thread_func(void*p)
{
client_element*client_p = (client_element*)p;
printf("started socked_thread_func for client: %s\n", client_p->id);
fd_set fdRead;
intret, lenth;
structtimeval aTime;
structmsg_headmsg_h;
char*buf = (char*)&msg_h;//from:2 char to 2 char msglenth:1 int
buf[0] = client_p->id[2];
buf[1] = client_p->id[3];
charmsg[100];
client_element*send_to;
structtcp_infoinfo;
inttcp_info_len=sizeof(info);
while(1)
{
FD_ZERO(&fdRead);
FD_SET(client_p->sockfd, &fdRead);
aTime.tv_sec = 2;
aTime.tv_usec = 0;
getsockopt(client_p->sockfd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&tcp_info_len);
if(info.tcpi_state == 1)
{
//printf("$$$%d tcp connection established...\n", client_p->sockfd);
;
}
else
{
printf("$$$%d tcp connection closed...\n", client_p->sockfd);
break;
}
ret = select( client_p->sockfd+1,&fdRead,NULL,NULL,&aTime );
if(ret > 0)
{
//判断是否读事件
if(FD_ISSET(client_p->sockfd, &fdRead))
{
//data available, so get it!
lenth = read( client_p->sockfd, buf+2, 6 );
if( lenth != 6 )
{
continue;
}
//对接收的数据进行处理,这里为简单的数据转发
lenth = read(client_p->sockfd, msg, msg_h.lenth);
if(lenth == msg_h.lenth)
{
send_to = find_client(clients, msg_h.to);
//printf("try to send to client %s\n", msg_h.to);
if(send_to == NULL)
{
printf("can't find target client\n");
continue;
}
write(send_to->sockfd, &msg_h,sizeof(structmsg_head));
write(send_to->sockfd, msg, lenth);
}
//处理完毕
}
}
}
close( client_p->sockfd);
del_client(clients, client_p->sockfd);
pthread_exit( NULL );
}

这里收到消息后就解析消息头,发送到指定的端口去(控制器或者显示进程),由于实际应用中socket传送数据可能存在分包的情况,客户需要自行定义消息的数据格式来保证数据的完整性,以及对数据进行更严格的验证。

另一方面对于已有的控制器来说,需要在原来的基础上进行修改,在主线程中与socket服务器建立连接:

sockedfd= socket(AF_INET,SOCK_STREAM,0);
address.sin_family=AF_INET;
address.sin_addr.s_addr= inet_addr("127.0.0.1");
address.sin_port= htons(9733);
len =sizeof(address);
do
{
res = connect(sockedfd, (structsockaddr*)&address, len);
if(res == -1)
{
perror("oops:connecterror");
}
}while(res == -1);
write(sockedfd,"IDG1",4);
printf("###connectedtoserver\n");

然后建立两个线程分别处理数据(data_thread_func)和命令(command_thread_func),其中data_thread_func用于监听硬件状态,并且发送相应的状态消息给socket服务器,而command_thread_func用于监听socket服务器的消息等待命令,用于改变硬件运行状态,不需要界面带有控制功能的客户可以不实现commad_thread_func。以GPIO控制器为例:

void*gpio_controller::data_thread_func(void* lparam)
{
gpio_controller *pSer = (gpio_controller*)lparam;
fd_set fdRead;
intret=0;
structtimeval aTime;
unsignedintpinstates = 0;
structmsg_head buf_h;
while( 1 )
{
FD_ZERO(&fdRead);
FD_SET(pSer->interface_fd,&fdRead);
aTime.tv_sec = 2;
aTime.tv_usec = 0;
//等待硬件消息,这里是GPIO状态改变
ret = select( pSer->interface_fd+1,&fdRead,NULL,NULL,&aTime );
if(ret < 0 )
{
//关闭
perror("select wrong");
pSer->close_interface(pSer->interface_fd);
break;
}
else
{
//select超时或者GPIO状态发生了改变,读取GPIO状态,发送给socket服务器
pinstates = INPINS;
ret = GPIO_PinState(pSer->interface_fd, &pinstates);
if(ret < 0)
{
printf("GPIO_PinState::failed %d\n", ret);
break;
}
sprintf((char*)&buf_h.to[0],"D1");
buf_h.lenth =sizeof(pinstates);
write(pSer->sockedfd, (void*)&buf_h.to[0], 6);
write(pSer->sockedfd, (void*)&pinstates,sizeof(pinstates));
}
}
printf("ReceiveThreadFunc finished\n");
pthread_exit( NULL );
}
void*gpio_controller::command_thread_func(void* lparam)
{
gpio_controller *pSer = (gpio_controller*)lparam;
fd_set fdRead;
intret, len;
structtimeval aTime;
structoutcom{
unsignedintoutpin;
unsignedintoutstate;
};
structoutcomout;
structmsg_head buf_h;
while( 1 )
{
FD_ZERO(&fdRead);
FD_SET(pSer->sockedfd,&fdRead);
aTime.tv_sec = 3;
aTime.tv_usec = 300000;
//等待socket服务器的消息
ret = select( pSer->sockedfd+1,&fdRead,NULL,NULL,&aTime );
if(ret < 0 )
{
//关闭
pSer->close_interface(pSer->interface_fd);
break;
}
if(ret > 0)
{
//判断是否读事件
if(FD_ISSET(pSer->sockedfd,&fdRead))
{
len = read(pSer->sockedfd, &buf_h,sizeof(buf_h));
//获取socket服务器发送的信息,进行解析
if(len !=sizeof(structoutcom))
{
printf("###invalid command lenth: %d, terminate\n", len);
}
len = read(pSer->sockedfd, &out, buf_h.lenth);
//write command
switch(out.outstate)
{
case0:
GPIO_OutClear(pSer->interface_fd, out.outpin);
if(ret < 0)
printf("GPIO_OutClear::failed %d\n", ret);
//printf("GPIO_OutClear::succeed %d\n", ret);
break;
case1:
GPIO_OutSet(pSer->interface_fd, out.outpin);
if(ret < 0)
printf("GPIO_OutSet::failed %d\n", ret);
//printf("GPIO_OutSet::succeed %d\n", ret);
break;
default:
printf("###wrong gpio state %d, no operation\n", out.outstate);
ret = -1;
break;
}
if(ret < 0)
break;
}
}
}
printf("ReceiveThreadFunc finished\n");
pthread_exit( NULL );
}

这里两个函数主要任务都是处理数据,data_thread_func使用select函数来等待输入GPIO的状态改变事件,如果有状态改变或者select等待超时都读取一次GPIO的状态,然后发送给socket服务器;command_thread_func监听服务器的消息,收到消息后进行解析,然后根据消息来操作GPIO输出信号

通过这两个函数便与socket服务器建立了消息沟通通道,而socket服务器会自动将数据转发到显示进程,这种实现可以使得对已有程序的改动降到很低的程度。实际实现中,可以在socket服务器中增加状态机等其他功能,记录硬件状态信息等。

4、显示程序

显示部分我们采用Qt来搭建,主要分为QML搭建的界面以及Qt c++编写的数据处理插件。QML是Qt提供的一种描述性的脚本语言,类似于css,可以在脚本里创建图形对象,并且支持各种图形特效,以及状态机等,同时又能跟Qt写的C++代码进行方便的交互,使用起来非常方便。采用QML加插件的方式主要是为了将界面设计与程序逻辑解耦,一般的系统开发中界面设计的变动往往多于后台逻辑,因此采用QML加插件的方式将界面设计与逻辑分离有利于开发人员的分工,加速产品迭代速度,降低后期维护成本。而且QML解释性语言的特性使得其语法更加简单,可以将界面设计部分交给专业的设计人员开发,而不要求设计人员会c++等编程语言。Qt底层对QML做了优化,将会优先使用硬件图形加速器进行界面的渲染,也针对触摸屏应用做了优化,使用QML能够更简单快捷的搭建流畅、优美的界面。QML也支持嵌入Javascript处理逻辑,但是底层逻辑处理使用Qt C++编写插件,能够更好的控制数据结构,数据处理也更加高效,Qt提供了多种方式将C++数据类型导入QML脚本中,更多详细资料可以查看Qt官方的文档。由于篇幅原因,我们在另外一篇文章:《使用QML进行界面开发》中更详细地介绍了QML及插件的实现,在此我们还是集中介绍socket消息处理部分。

本例程中数据处理插件的任务就是连接socket服务器,与服务器进行通信,接收消息进行解析然后提供给QML界面,以及从QML界面获取消息给socket服务器发送命令。插件中通过socket进行通信的部分代码如下:

voidMsgClient::cServer(void* param)
{
MsgClient*client = (MsgClient*)param;
intret;
intlen;
structsockaddr_inaddress;
intsockedfd = socket(AF_INET,SOCK_STREAM,0);
printf("sockedfd:%d\n", sockedfd);
client->sockedfd= sockedfd;
address.sin_family=AF_INET;
address.sin_addr.s_addr= inet_addr("127.0.0.1");//本地IP通信
address.sin_port= htons(9733);
len =sizeof(address);
do
{
printf("Client:connecting...\n");
ret = ::connect(sockedfd, (structsockaddr*)&address, len);//建立连接
if(ret == -1)
{
perror("oops:connecttoservererror");
}
sleep(2);
}while(ret == -1);
write(sockedfd,"IDD1",4);
printf("Client:connectedtoserver\n");
emitclient->serverConnected();
fd_setfdRead;
structtimevalaTime;
charbuf[100];
unsignedintpinstates;
structmsg_headbuf_h;
while(!client->exit_flag)
{
FD_ZERO(&fdRead);
FD_SET(sockedfd, &fdRead);
aTime.tv_sec=3;
aTime.tv_usec=0;
ret = select(sockedfd+1, &fdRead,NULL,NULL, &aTime);//等待消息
if(ret < 0)
{
perror("sometingwrongwithselect");
}
if(ret >0)
{
if(FD_ISSET(sockedfd, &fdRead))
{
len = read(sockedfd, &buf_h,sizeof(buf_h));
inti;
switch(buf_h.from[0]) {//解析消息
case'S':
//串口信息
i = buf_h.from[1] -'0';
len = read(sockedfd, buf, buf_h.lenth);
client->rmsgQueue[i] << buf;
if(i == client->m_interface)
emitclient->newMsgRcved();
memset(buf,0,sizeof(buf));
break;
case'G':
//GPIO信息
len = read(sockedfd, &pinstates, buf_h.lenth);
printf("getGPIOpinstates\n");
client->updateGPIOState(pinstates);
break;
default:
break;
}
}
}
}
close(sockedfd);
pthread_exit(NULL);
}

如代码所示,插件首先通过本地IP127.0.0.1与socket服务器建立连接(connect),然后等待socket服务器的消息(select),收到消息后进行解析,判断是哪个硬件控制器发送的消息,然后更新相应的显示界面,这里的代码相对简单,只是为了展示通过本地IP实现显示进程与控制进程之间的通信,实际使用中客户需要对数据进行更严格的检验。

使用QML搭建串口控制界面如下图所示:

GPIO控制器的显示效果如下:

由于篇幅原因,我们在此不详细介绍实现界面的QML脚本了,将会在另一篇文章中进行专门的介绍,感兴趣的用户可以关注我们官网上的文章更新,或者向我们要取程序源码。用户在实际开发中可以参考此方式实现显示进程与控制进程之间的通信,从而实现单独的显示进程,对已有的控制进程的更改控制到很小的程度,一方面减少了由于程序修改而造成控制程序的不稳定,另一方面使用QML又能快速的搭建界面,解决显示设备状态的需求。

5、总结

实际测试过程中,我们在ESM6802工控板上运行本文介绍的程序,底层控制程序直接可以开机后台运行,显示程序开机后手动加载,通过本地IP地址与控制程序的socket服务器连接,然后实时更新系统状态,也能及时响应人工控制,如改变输出GPIO的输出状态,关掉显示程序之后,控制程序继续正常运行,之后还可以再次启动显示程序。

将底层控制与显示分开后,程序开发分工可以更加细致,也一定程度上增加了控制系统的稳定性,减小了维护成本。同时使用QML进行界面开发能够更加方便快速的更新系统的显示效果,完成产品迭代。由于底层控制与显示之间采用socket进行通信,显示部分也可以采用其他的开发环境,比如ESM6802也支持Android开发,用户在产品升级换代的时候就能够直接沿用底层控制部分的程序,而只对上层显示部分的程序进行调整。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式主板
    +关注

    关注

    7

    文章

    6085

    浏览量

    35334
  • 安卓
    +关注

    关注

    5

    文章

    2130

    浏览量

    57198
收藏 人收藏

    评论

    相关推荐

    飞腾助力首届教育信息技术应用创新大赛圆满落幕

    近日,由中国教育技术协会主办,教育部教育管理信息中心教育信实验室、公安部第三研究所、中国电子工业标准化技术协会信工委会支持,北京航空航天
    的头像 发表于 12-24 09:57 100次阅读

    有方科技参编的信息技术团体标准发布

    近日,有方科技参编的《信息技术产品供应链成熟度 共性指标 第1部分:企业背景评价》《信息技术产品供应链成熟度 共性指标 第2部分:技术掌控评价》《信息技术产品供应链成熟度 共性指标 第
    的头像 发表于 12-23 10:44 115次阅读

    龙芯中科助力2024首届教育信息技术应用创新大赛成功举办

    近日,2024首届教育信息技术应用创新大赛在北京航空航天大学成功举办。本次大赛由中国教育技术协会主办,教育信实验室、公安部第三研究所、中国电子工业标准化技术协会信
    的头像 发表于 12-19 17:02 208次阅读

    拓维信息参与牵头组建!长沙新一代信息技术产教联合体正式获批

    的长沙新一代信息技术产教联合体成功获批,为长沙市信息技术产业发展注入了新的活力。图/《2024年长沙市市级市域产教联合体名单》长沙新一代信息技术产教联合体将依托龙头企
    的头像 发表于 12-07 01:06 369次阅读
    拓维<b class='flag-5'>信息</b>参与牵头组建!长沙新一代<b class='flag-5'>信息技术</b>产教联合体正式获批

    一文搞懂Linux进程的睡眠和唤醒

    一、常见的进程状态与理解 在操作系统内部,有专门用来管理进程的结构体,叫做struct task_struct,也称作进程控制块(PCB),主要包含描述进程的相关
    发表于 11-04 15:15

    中科达荣获2024年软件和信息技术服务优秀企业

    及前百家企业”名单。中科达凭借非凡的技术实力与持续的创新能力,成功入选“2024年度软件和信息技术服务竞争力百强企业”以及“2024年软件和信息技术服务优秀企业”。
    的头像 发表于 10-30 11:44 485次阅读

    Linux用户身份与进程权限详解

    在学习 Linux 系统权限相关的主题时,我们首先关注的基本都是文件的 ugo 权限。ugo 权限信息是文件的属性,它指明了用户与文件之间的关系。但是真正操作文件的却是进程,也就是说用户所拥有的文件
    的头像 发表于 10-23 11:41 351次阅读
    <b class='flag-5'>Linux</b>用户身份与<b class='flag-5'>进程</b>权限详解

    国产化背景下的工控主板发展现状

    ,是信息技术应用创新产业的简称,于2016年“信工委会”(信息技术应用创新工作委员会)提出,目的就是要推动我们国内软硬件关键技术的研发
    的头像 发表于 09-21 16:15 401次阅读

    梯度科技入选2023年信息技术应用创新解决方案名单

    日前,工业和信息化部网络安全产业发展中心(工业和信息化部信息中心)在天津举办2024信息技术应用创新发展大会暨解决方案应用推广大会。会上正式公布了2023年
    的头像 发表于 09-09 16:29 432次阅读

    中软国际信服务助力大连信产业发展

    为进一步激发大连本地信产业生态的活力与潜力,搭建一个高效、开放的交流平台。由大连软件行业协会携手大连市信息技术应用创新综合服务中心主办,中软国际协办的,2024年大连市信息技术应用创新产业发展大会
    的头像 发表于 08-27 16:49 879次阅读

    深入探讨Linux进程调度器

    Linux操作系统作为一个开源且广泛应用的操作系统,其内核设计包含了许多核心功能,而进程调度器(Scheduler)就是其中一个至关重要的模块。进程调度器负责决定在任何给定的时刻哪个进程
    的头像 发表于 08-13 13:36 948次阅读
    深入探讨<b class='flag-5'>Linux</b>的<b class='flag-5'>进程</b>调度器

    龙芯中科三项信方案入围工信部2023年信息技术应用创新应用示范案例名单

    近日,工业和信息化部通报了2023年信息技术应用创新解决方案征集遴选结果,本次共评选出典型解决方案173个、应用示范案例83个、单项创新案例64个。
    的头像 发表于 03-07 16:45 934次阅读
    龙芯中科三项信<b class='flag-5'>创</b>方案入围工信部2023年<b class='flag-5'>信息技术</b>应用创新应用示范案例名单

    RX78M组 EtherCAT ETG.5003示例程序固件信息技术

    电子发烧友网站提供《RX78M组 EtherCAT ETG.5003示例程序固件信息技术.pdf》资料免费下载
    发表于 02-21 14:22 1次下载
    RX78M组  EtherCAT ETG.5003<b class='flag-5'>示例</b>程序固件<b class='flag-5'>信息技术</b>

    软通动力与捷技术签订战略合作协议

    近日,软通动力信息技术(集团)股份有限公司(以下简称“软通动力”)与宁波捷技术股份有限公司(以下简称“捷技术”)正式签订战略合作协议,并
    的头像 发表于 02-03 16:35 1119次阅读

    浅谈Linux进程

    进程和程序的区别: 进程是动态的,程序是静态的 一、进程的创建(fork()函数) int main(){ pid_t pid; pid=fork(); if(pid     >0
    的头像 发表于 01-28 15:54 259次阅读
    浅谈<b class='flag-5'>Linux</b>的<b class='flag-5'>进程</b>