Linux-进程、进程组、作业、会话、控制终端详解

From:http://www.cnblogs.com/JohnABC/p/4079669.html

Linux进程优先级的处理--Linux进程的管理与调度(二十二):http://blog.csdn.net/gatieme/article/details/51719208

进程 、进程组、会话、控制终端之间的关系:http://blog.csdn.net/yh1548503342/article/details/41891047

Linux进程控制:http://www.cnblogs.com/cpsmile/p/4382106.html


详细可以查看 APUE 第八章 进程控制 和 第九章 进程关系


一、进程


  传统上,Unix操作系统下运行的应用程序、 服务器以及其他程序都被称为进程,而Linux也继承了来自unix进程的概念。必须要理解下,程序是指的存储在存储设备上(如磁盘)包含了可执行机器指 令(二进制代码)和数据的静态实体;而进程可以认为是已经被OS从磁盘加载到内存上的、动态的、可运行的指令与数据的集合,是在运行的动态实体。这里指的 指令和数据的集合可以理解为Linux上ELF文件格式中的.text .data数据段。


二、进程组


  每个进程除了有一个进程ID之外,还属于一个进程组,那什么是进程组呢?

  顾名思义,进程组就是一个或多个进程的集合。这些进程并不是孤立的,他们彼此之间或者存在父子、兄弟关系,或者在功能上有相近的联系。每个进程都有父进程,而所有的进程以init进程为根,形成一个树状结构同一进程组中的各进程接收来自同一终端的各种信号,每个进程组有一个唯一的进程组ID。每个进程组有一个组长进程,该组长进程的ID等于进程组ID。从进程组创建开始到最后一个进程离开为止的时间称为进程组的生命周期

  那为啥Linux里要有进程组呢?其实,提供进程组就是为了方便对进程进行管理。假设要完成一个任务,需要同时并发100个进程。当用户处于某种原因要终止 这个任务时,要是没有进程组,就需要手动的一个个去杀死这100个进程,并且必须要严格按照进程间父子兄弟关系顺序,否则会扰乱进程树。有了进程组,就可以将这100个进程设置为一个进程组,它们共有1个组号(pgrp),并且有选取一个进程作为组长(通常是“辈分”最高的那个,通常该进程的ID也就作为进程组的ID)。现在就可以通过杀死整个进程组,来关闭这100个进程,并且是严格有序的。组长进程可以创建一个进程组,创建该组中的进程,然后终止。只要在某个进程组中一个进程存在,则该进程组就存在,这与其组长进程是否终止无关

   进程必定属于一个进程组,也只能属于一个进程组 一个进程组中可以包含多个进程。 进程组的生命周期从被创建开始,到其内所有进程终止或离开该组。

  内核中,sys_getpgrp()系统调用用来获取当前进程所在进程组号;sys_setpgid(int pid, int pgid)调用用来设置置顶进程pid的进程组号为pgid。

#include <unistd.h>pid_t getpgrp(void);    // 返回值:调用进程的进程组IDint setpgid(pid_t pid, pid_t pgid);    // 返回值:成功,返回0;失败,返回-1说明:
setpgid用于添加进程到一个现有的进程组,或者创建一个新的进程组。函数将进程ID为pid的进程加入ID为pgid的进程组中。如果pid == pgid,则pid指定的进程变为进程组长;
如果pid == 0,则使用调用者的进程ID;
如果pgid == 0,则将pid用作进程组ID。


三、作业


  Shell分前后台来控制的,不是进程,而是作业(Job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成,Shell可以运行一个前台作业任意多个后台作业,这称为作业控制。

  作业与进程组的区别:如果作业中的某个进程又创建了子进程,则子进程不属于作业。一旦作业运行结束,Shell就把自己提到前台,如果原来的前台进程还存在(如果这个子进程还没终止),它自动变为后台进程组。

        作业控制允许在一个终端上启动多个作业(进程组),哪一个作业可以访问该终端以及哪些作业在后台运行。从shell使用作业控制的角度看,用户可以在前台或者后台启动一个作业,例如:vi main.c在前台启动只有一个进程组成的作业,而make all &在后台启动只有一个进程组成的作业

我们可以键入3个特殊字符使得终端程序产生信号,并将它们发送到前台进程组:中断字符(Ctrl + C)产生SIGINT信号;退出字符(Ctrl + \)产生SIGQUIT信号;挂起字符(Ctrl + Z)产生SIGTSTP信号;

只有前台作业才可以接收终端上输入的字符,如果后台作业试图都终端,那么终端驱动程序向后台作业发送特定信号SIGTTIN,该信号将停止此后台作业,而shell则向用户发送通知,然后用户就可以利用shell命令fg将此作业转为前台作业运行。但是如果后台作业输出到控制终端又将发生什么呢?我们可以通过stty命令禁止这种情况。此时,终端驱动程序向后台作业发送SIGTTOU信号,使其进程阻塞,当然此时我们也可以利用fg将其移到前台运行。


四、会话


  再看下会话。由于Linux是多用户多任务的分时系统,所以必须要支持多个用户同时使用一个操作系统。当一个用户登录一次系统就形成一次会话 。会话是一个或者多个进程组的集合。进程组通常是由shell管道编制在一起的。一个会话可包含多个进程组,但只能有一个前台进程组。每个会话都有一个会话首领(leader),即创建会话的进程。 sys_setsid()调用能创建一个会话。必须注意的是,只有当前进程不是进程组的组长时,才能创建一个新的会话。调用setsid 之后,该进程成为新会话的leader。

  一个会话可以有一个控制终端。这通常是登陆到其上的终端设备(在终端登陆情况下)或伪终端设备(在网络登陆情况下)。建立与控制终端连接的会话首进程被称为控制进程。一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组。 

  一次登录形成一个会话

  一个会话可包含多个进程组,但只能有一个前台进程组


会话是一个或者多个进程组的集合。进程组通常是由shell管道编制在一起的,如下:


进程调用setsid函数创建一个新的会话:

#include <unistd.h>pid_t setsid(void);    // 返回值:成功,返回进程组ID;失败,返回-1说明:
如果调用此函的进程不是一个进程组长,则此函数创建一个新的会话,具体如下:
(1)   该进程变为新会话的会话首进程。
(2)   该进程成为一个新进程组的组成进程,新进程组ID是该进程ID。
(3)   该进程没有控制终端。pid_t getsid(pid_t pid);    // 返回值:成功,返回会话首进程的进程组ID;失败,返回-1说明:getsid(0)返回调用进程的会话首进程的进程组ID,如果pid不属于调用者所在的会话,则不返回。


五、控制终端


  会话的领头进程打开一个终端之后, 该终端就成为该会话的控制终端 (SVR4/Linux)  

  与控制终端建立连接的会话领头进程称为控制进程 (session leader) 

  一个会话只能有一个控制终端 

  产生在控制终端上的输入和信号将发送给会话的前台进程组中的所有进程 

  终端上的连接断开时 (比如网络断开或 Modem 断开), 挂起信号将发送到控制进程(session leader)

 

  进程属于一个进程组,进程组属于一个会话,会话可能有也可能没有控制终端。一般而言,当用户在某个终端上登录时,一个新的会话就开始了。进程组由组中的领头进程标识,领头进程的进程标识符就是进程组的组标识符。类似地,每个会话也对应有一个领头进程

  同一会话中的进程通过该会话的领头进程和一个终端相连,该终端作为这个会话的控制终端。一个会话只能有一个控制终端,而一个控制终端只能控制一个会话。用户通过控制终端,可以向该控制终端所控制的会话中的进程发送键盘信号。

 

   同一会话中只能有一个前台进程组,属于前台进程组的进程可从控制终端获得输入,而其他进程均是后台进程,可能分属于不同的后台进程组。

   当我们打开多个终端窗口时,实际上就创建了多个终端会话。每个会话都会有自己的前台工作和后台工作

一个会话可以有一个控制终端,通常是终端设备或伪终端设备。建立与控制终端连接的会话首进程被称为控制进程。一个会话中的进程组可被分为一个前台进程组和一个后台进程组。需要有一种方法通知内核哪一个进程组是前台进程组,这样便于终端设备驱动程序知道将终端输入和终端产生的信号发送到何处:

#include <unistd.h>pid_t tcgetpgrp(int fd);    // 返回值:成功,返回前台进程组ID;失败,返回-1int tcsetpgrp(int fd, pid_pgrpid);    // 返回值:成功,返回0;失败,返回-1说明:其中,fd为相关联的打开终端。大多数应用程序并不直接调用这两个函数,而是由作业控制shell调用。需要管理控制终端的应用程序可以调用tcgetsid函数获得控制终端的会话首进程的进程组ID:#include <termios.h>
pid_t tcgetsid(int fd);    // 返回值:成功,返回会话首进程的进程组ID;失败,返回-1


六、守护进程


守护进程(daemon)常常在系统引导装入时启动,在系统关闭时终止。由于守护进程没有控制终端(其终端名设置为?),因此,其在后台运行。大多守护进程都以超级用户权限执行

编程规则

(1)   首先调用umask将文件模式创建屏蔽字设置为一个已知数值(通常是0)。这样做是防止继承而来的屏蔽字没有某些权限,尤其是写权限。

(2)   调用fork,然后使父进程exit。

(3)   调用setsid创建一个新会话。

(4)   将当前工作目录更改为根目录。因为守护进程通常在系统引导之前就存在,如果守护进程的当前工作目录在一个需要挂载的文件系统上,那么该文件系统不能被卸载。也有某些守护进程会把当前工作目录更改到某个指定的位置,例如行式打印机假脱机守护进程就可能将其工作目录更改到它们的spool目录上。

(5)   关闭不再需要的文件描述符。可以使用open_max函数或者getrlimit函数获取最高文件描述符值,然后关闭直到该值的所有文件描述符。这样做可以避免守护进程从其父进程继承任何文件描述符。

(6)   某些守护进程将文件描述符0、1、2指向/dev/null。这样可以使得任何以恶搞试图读标准输入、写标准输出、写标准错误输出的程序不产生任何效果。由于守护进程是在后台运行的,因此登录会话的终止并不影响守护进程。如果其他用户在同一终端设备上登录,我们自然不希望在该终端上见到守护进程的输出,用户也不希望他们在终端上的输入被守护进程读取,因此上述措施是相当有用的

如下程序可由想要初始化为守护进程的程序调用,在main函数中调用函数daemonize,然后使main进程进入休眠状态,通过ps –efj命名查看进程状态,可以发现守护进程init,其终端名为 ?
[root@benxintuzi process]# cat init.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>void daemonize(const char *cmd)
{int                     i, fd0, fd1, fd2;pid_t                   pid;struct rlimit          rl;struct sigaction       sa;/**       * Clear file creation mask.*               */umask(0);/**       * Get maximum number of file descriptors.*               */if (getrlimit(RLIMIT_NOFILE, &rl) < 0){printf("%s: can't get file limit\n", cmd);return ;}/**       * Become a session leader to lose controlling TTY.*               */if ((pid = fork()) < 0){printf("%s: can't fork\n", cmd);return ;}else if (pid != 0) /* parent */exit(0);setsid();/**       * Ensure future opens won't allocate controlling TTYs.*               */sa.sa_handler = SIG_IGN;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;if (sigaction(SIGHUP, &sa, NULL) < 0){printf("%s: can't ignore SIGHUP\n", cmd);return ;}if ((pid = fork()) < 0){printf("%s: can't fork\n", cmd);return ;}else if (pid != 0) /* parent */exit(0);/**       * Change the current working directory to the root so*               * we won't prevent file systems from being unmounted.*                       */if (chdir("/") < 0){printf("%s: can't change directory to \n", cmd);}/**       * Close all open file descriptors.*               */if (rl.rlim_max == RLIM_INFINITY)rl.rlim_max = 1024;for (i = 0; i < rl.rlim_max; i++)close(i);/**       * Attach file descriptors 0, 1, and 2 to /dev/null.*               */fd0 = open("/dev/null", O_RDWR);fd1 = dup(0);fd2 = dup(0);/**       * Initialize the log file.*               */openlog(cmd, LOG_CONS, LOG_DAEMON);if (fd0 != 0 || fd1 != 1 || fd2 != 2) {syslog(LOG_ERR, "unexpected file descriptors %d %d %d",fd0, fd1, fd2);exit(1);}
}int main(void)
{daemonize("ps");sleep(30);return 0;
}[root@benxintuzi process]# ./init
[root@benxintuzi process]# ps -efj
UID        PID  PPID  PGID   SID  C STIME TTY          TIME CMD
root         1     0     1     1  0 19:35 ?        00:00:03 /sbin/init
root      2198  2174  2198  2057  0 19:38 pts/0    00:00:00 vim init.c
root      2237     1  2236  2236  0 19:45 ?        00:00:00 ./init
root      2238  2188  2238  2100  0 19:45 pts/1    00:00:00 ps -efj


APUE 学习记录


      一个进程组可以包含多个进程

      进程组中的这些进程之间不是孤立的,他们彼此之间或者存在者父子、兄弟关系,或者在功能有相近的联系。

      那linux为什么要有进程组呢?其实提供进程组就是方便管理这些进程。

      假设要完成一个任务,需要同时并发100个进程,当用户由于某种原因要终止这个任务时,要是没有进程组,就需要一个个去杀死这些进程,设置了进程组之后,就可以对进程组中的每个进程进行杀死。  

     每个进程必定属于一个进程组,也只能属于一个进程组。  

     一个进程除了有进程ID外,还有一个进程组ID,每个进程组也有唯一的进程组ID。

     每个进程组有一个进程组组长,进程组组长的进程ID和组ID相同


函数 getpgrp 和 getpgid 可以返回调用进程的进程组ID

#include <unistd.h>pid_t getpgrp(void);
pid_t getpgid(pid_t pid);
//返回值:成功则返回进程组ID,失败返回-1.

函数 setpgid 可以使进程加入现有的组或者创建一个新进程组。

#include <unistd.h>int setpgid(pid_t pid, pid_t pgid );setpgid将pid进程的进程组ID设置为pgid.
(1)pid=pgid ,则表示将pid指定的进程设为进程组组长
(2)pid=0 .则使用调用进程的进程ID
(3)pgid=0,则将pid指定进程用作进程组ID。

一个会话又可以包含多个进程组。一个会话对应一个控制终端

  

    linux是一个多用户多任务的分时操作系统,必须要支持多个用户同时登陆同一个操作系统,当一个用户登陆一次终端时就会产生一个会话,

  每个会话有一个会话首进程,即创建会话的进程,建立与终端连接的就是这个会话首进程,也被称为控制进程。一个会话可以包括多个进程组,

   这些进程组可被分为一个前台进程组和一个或多个后台进程组。为什么要这么分呢?前台进程组是指需要与终端进行交互的进程组(只能有一个)

  比如有些进程是需要完成IO操作的,那么这个进程就会被设置为前台进程组.当我们键入终端的中断键和退出键时,就会将信号发送到前台进程

  组中的所有进程。而 后台进程组是指不需要与终端进程交互的进程组,比如:一些进程不需要完成IO 操作,或者一些守护进程就会 被设置为后台进程组(可以有多个),

  (这是我的理解,不知道对错)。  如果终端接口检测到网络已经断开连接,则会将挂断信号发送给会话首进程。

            

进程调用 setsid 函数建立一个新会话.

#include <unistd.h
#include <unistd.h>pid_t  setsid(pid_t pid);
//返回:成功则返回进程组ID,失败则返回-1.

如果调用次函数的进程不是进程组的组长,则会创建一个新会话,结果将发生下面3件事情:

(1)该进程会变为新会话的首进程。

(2)该进程会成为一个新进程组的组长进程

(3)该进程没有控制终端。


如果该调用进程已经是一个进程组的组长,则调用会出错。

为了保证不会出错,通常先fork一个子进程,在关闭父进程,因为子进程继承了父进程的进程组ID,

而进程iD则是新分配的,两者不可能相等,从而保证了子进程不会是进程组组长。(后面编写守护进程时会用到。)


怎样编写守护进程?


1. 在后台运行。 
为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。 

if(pid=fork()) exit(0);//是父进程,结束父进程,子进程继续 


2. 脱离控制终端,登录会话和进程组 
有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。 
控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长: 
setsid(); 
说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。 

3. 禁止进程重新打开控制终端 
现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端: 

if(pid=fork()) exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长) 

4. 关闭打开的文件描述符 
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们: 
for(i=0; i 关闭打开的文件描述符; close(i);)


5. 改变当前工作目录 
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如/tmpchdir("/") 

6. 重设文件创建掩模 
进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0); 

7. 处理SIGCHLD信号 
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。 
signal(SIGCHLD,SIG_IGN); 
这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。 


daemontest.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
void init_daemon()
{pid_t pid ;int i ;pid =fork();if(pid<0){printf("fork error secondly!\n");exit(1);}else if(pid>0)//结束父进程{printf("this is first parent process!\n");exit(0);}//子进程继续运行setsid() ;//前面为setsid正确调用提供了前提,使子进程成为新的会话组长和//新的进程组长pid=fork();if(pid<0)//子进程成为无终端的会话组长,但是还是可以打开终端,为了//使进程脱离终端,使之成为不是会话组长{printf(" fork error secondly!\n");exit(1);}else if(pid>0)//关闭第一个子进程{printf("this is first child process!\n");exit(0);}//第二个子进程继续运行for(i=0;i<NOFILE;i++){close(i);}chdir("/tmp");umask(0);return;
}main.c
#include <stdio.h>
#include <stdlib.h>
void init_daemon(void);int main(void)
{FILE *fp ;init_daemon() ;while(1){if((fp=fopen("daemon.log","a"))>=0){fprintf(fp,"%s","good");fclose(fp);sleep(10);}}exit(0);
}


运行:

yuan@YUAN:~$ ./daemontest
this is first parent process!
this is first child process!
yuan@YUAN:~$ ps -axj


结果:

PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND1  2970  2969  2969 ?           -1 S     1000   0:00 ./daemontest




本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/496340.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浅谈项目开发现状(一)

在现在的软件开发中&#xff0c;一些大的软件公司有充分的资金&#xff0c;所以他的公司人员组织架构能组成&#xff1a;需求分析团队&#xff08;为了更好的了解用户的完整需求&#xff09;--->研发团队&#xff08;通过计算机语言来实现用户需求&#xff09;&#xff0c;方…

波士顿咨询:2018最具创新力企业50强

来源&#xff1a;前瞻网在波士顿咨询公司评选的2018年最具创新力公司中&#xff0c;有11家公司——其中包括前10名中的7家——都是“数字原住民”&#xff0c;按定义也就是“数字创新者”。榜单上大多数公司已经将数字技术建立在他们的创新计划中。这一趋势在各个行业都很普遍&…

Linux 线程

Linux 的多线程编程的高效开发经验&#xff1a;https://www.ibm.com/developerworks/cn/linux/l-cn-mthreadps/ linux线程的实现&#xff1a;http://www.cnblogs.com/zhaoyl/p/3620204.html 线程概念经典解析&#xff1a;http://blog.chinaunix.net/uid-29613952-id-421477…

Spring Data JPA 从入门到精通~JpaSpecificationExecutor实现原理

JpaSpecificationExecutor 实现原理 我们还是先通过开发工具&#xff0c;把关键的类添加到Diagram上面进行分析&#xff0c;如图&#xff1a; 我们通过上图可以看一下&#xff0c;前面介绍的几个类之间的关联关系。 SimpleJpaRepository 实现类中的关键源码如下&#xff1a; …

微信发布首份《移动支付时代的无人零售报告》

来源&#xff1a;爱范儿 作者&#xff1a;Panda3 月 30 日&#xff0c;微信支付行业运营总监白振杰在 2018 智慧无人零售大会上发布了《移动支付时代的无人零售行业报告》&#xff0c;报告首次结合中国百货商业协会权威调研和微信支付的数据分析能力&#xff0c;揭示了移动支…

盘点《头号玩家》里的 VR 技术,现在就能造个 Oasis 出来

来源&#xff1a;沉浸感丨公众号 作者&#xff1a; 刘芳平由史蒂文斯皮尔伯格导演的科幻电影《头号玩家》&#xff08;Ready One Player&#xff09;于 3 月 30 日在中国大陆上映。首个周末便收获广泛好评&#xff0c;登上豆瓣电影本周口碑榜第一名&#xff0c;截止 31 日晚积…

find()matlab,Matlab 之 find()函数

当我第一次用matlab语言编写一个工程项目时&#xff0c;发现自己编写的脚本里循环特别多&#xff0c;导致编程效率很低&#xff0c;这让我特别苦恼。有一次导师让我阅读他编写的一个Matlab脚本&#xff0c;并按照新要求对其进行更改。我发现脚本里多次用到find()函数&#xff0…

一图看懂阿里云IoT战略

来源&#xff1a;云栖社区从万物互联到万物智联&#xff0c;离不开“无处不在的计算”。阿里云IoT在云边端部署了可协同的计算&#xff0c;基于物联网使能平台联合开发者、芯片模组厂商、行业合作伙伴等在城市、生活、汽车、制造四大领域已完成深度布局&#xff0c;即一朵云、两…

matlab机器人模型仿真,一知半解|MATLAB机器人建模与仿真控制(1)

各位机器人技术爱好者们&#xff0c;大家好&#xff01;很荣幸受古月老师邀请&#xff0c;成为古月居的签约作者。希望在这里可以同大家相互交流与学习。始终觉得自己能力不足&#xff0c;但终于还是硬着头皮开始了在古月居的第一篇博客。由于我是机械专业出身&#xff0c;在接…

第四期《Summer Tree》 已经整理完毕

虽然已经整理完毕&#xff0c;但是却没有空间上传&#xff0c;由于太大了上传不了.下次再补充回来 如果地址下载不到&#xff0c;告诉我。呵呵 我上传在微软共享空间里面 在这里可以下载在这里转载于:https://www.cnblogs.com/hero82748274/archive/2009/06/06/1497771.html

研究报告:城市大脑的起源、现状与未来趋势

报告撰写人&#xff1a;刘锋 《互联网进化论》作者&#xff0c;计算机博士2009年1月&#xff0c;IBM公司首席执行官彭明盛首次提出“智慧地球”&#xff0c;建议政府投资新一代的智慧型基础设施。此后智慧城市建设在世界范围内展开&#xff0c;在中国有上百个地区提出建设“智慧…

Linux IO - 同步,异步,阻塞,非阻塞

From&#xff1a;http://blog.csdn.net/historyasamirror/article/details/5778378 同步/异步&#xff0c;阻塞/非阻塞概念深度解析&#xff1a;http://blog.csdn.net/lengxiao1993/article/details/78154467 知乎上关于 阻塞和非阻塞、同步和异步 之间区别的生动解释。 htt…

微软 AI 设计原则:成为弱者,再带来惊喜

来源&#xff1a;36Kr 作者&#xff1a;木木子编者按&#xff1a;AI设计的思路是什么&#xff1f;更完美&#xff1f;更能想用户之所想&#xff1f;本文作者Cliff Kuang在“The company studied personal assistants–human ones–to understand how to make a great machine…

vim 中的杀手级插件: vundle (vim 插件管理器)

From&#xff1a;http://zuyunfei.com/2013/04/12/killer-plugin-of-vim-vundle/ vundle.txt&#xff1a;https://github.com/VundleVim/Vundle.vim/blob/master/doc/vundle.txt Vundle 的具体介绍和配置&#xff1a;github repo&#xff1a;https://github.com/gmarik/vundl…

Spring Data JPA 从入门到精通~Auditing及其事件详解

Auditing 及其事件详解 Auditing 翻译过来是审计和审核&#xff0c;Spring 的优秀之处在于帮我们想到了很多繁琐事情的解决方案&#xff0c;我们在实际的业务系统中&#xff0c;针对一张表的操作大部分是需要记录谁什么时间创建的&#xff0c;谁什么时间修改的&#xff0c;并且…

未来城市的无人机送货系统是怎样的?

来源&#xff1a; 资本实验室 作者&#xff1a;李鑫Siri通过移动应用下了一盒巴克拉拉面膜的订单&#xff0c;电商平台收到信息&#xff0c;发指令给最近的送货驳船。驳船上的无人机获取货物包裹后直接飞向Siri家。在不到8分钟的飞行后&#xff0c;无人机来到位于12层的Siri家…

Vim自动补全神器:YouCompleteMe

From&#xff1a;http://www.jianshu.com/p/d908ce81017a github 地址&#xff1a;https://github.com/Valloric/YouCompleteMe YouCompleteMe is a fast, as-you-type, fuzzy-search code completion engine for Vim. 参考&#xff1a; https://github.com/Valloric/YouCom…

对比 | 欧洲、美国、中国智慧城市的不同实践路径

来源&#xff1a;《上海城市规划》2018年第1期《欧美智慧城市最新实践与参考》作者&#xff1a;刘杨 龚烁 刘晋媛随着ICT、大数据、物联网等各类新兴技术的不断发展&#xff0c;智慧城市的运营和实践也不断趋于成熟。通过整理欧美各大典型智慧城市的最新实践案例&#xff0c;总…

C++ 使用 TinyXml 解析 XML 文件

知乎 C解析xml有什么好用的轮子? &#xff1a;https://www.zhihu.com/question/32046606 TinyXML-2 的 github地址和帮助文档&#xff1a;https://github.com/leethomason/tinyxml2 tinyxml 下载地址&#xff1a;https://sourceforge.net/projects/tinyxml/ *Please Note*…

定制自己的Windows CE 5.0 ARM中文模拟器(转)

定制自己的Windows CE 5.0 ARM中文模拟器(转)http://showvi.com/Blog/ViewAirticle/59一、生成OS 1. 安装Windows CE 5.0&#xff08;记得CPU类型里把ARMV4I选上&#xff09;装完之后装DeviceEmulatorBSP.msi&#xff0c;这是ARMV4I模拟器的BSP。 我补充个DeviceEmulatorBSP.ms…