期中之后的第一个lab 就是实现一个简单的Shell 程序,程序的大部分已经写好,只需要实现 eval 函数和处理信号的sigchld_handle, sigint_handle, sigtstp_handle这三个函数。 这个lab 主要要求处理好各个信号,因为上课的时候一直听得很糊涂,就拖着没有写,直到这两天deadline逼近才动手。同样是时间紧迫,debug的时候出了很多问题,在网上搜了很多解答,但是因为题目版本不一样,并不完全适用,比如之前的不需要重定向。因此把自己写的代码也贴出来,最后是一些自己的心得。

        这里还有shell lab的所有文件压缩包提供下载
 

一些心得:

  • 测试的时候的一些奇葩函数, mytstpp 向shell 发出一个SIGTSTP,而mytstps 向自己的进程发出SIGTSTP信号。前者shell 会调用sigtstp_handle 函数,而后者会使子进程stop,然后向shell 发出SIGCHLD信号,shell调用sigchld_handle 函数。这就要求我们分清楚每一个信号会由哪个进程(函数)处理。
  • shell收到的每个SIGTDTP,SIGINT信号都要发给前台进程,而这个前台进程是由自己的job_list 列表维护的,而实际上每个子进程的停止,终止都是由信号操作。
  • 调试的时候可以用GDB,但是涉及到信号和线程,可以在网上搜一下有很好的教程。
  • 通过运行tshref 可以找到每个命令的标准输出,这个稍微注意一下就可以了。
就写到这里了,如果有错误,烦请指正,同时欢迎交流!
 
代码附下:
 /*
* tsh - A tiny shell program with job control
*
* 2014.12.1
*
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <errno.h> /* Misc manifest constants */
#define MAXLINE 1024 /* max line size */
#define MAXARGS 128 /* max args on a command line */
#define MAXJOBS 16 /* max jobs at any point in time */
#define MAXJID 1<<16 /* max job ID */ /* Job states */
#define UNDEF 0 /* undefined */
#define FG 1 /* running in foreground */
#define BG 2 /* running in background */
#define ST 3 /* stopped */ /*
* Jobs states: FG (foreground), BG (background), ST (stopped)
* Job state transitions and enabling actions:
* FG -> ST : ctrl-z
* ST -> FG : fg command
* ST -> BG : bg command
* BG -> FG : fg command
* At most 1 job can be in the FG state.
*/ /* Parsing states */
#define ST_NORMAL 0x0 /* next token is an argument */
#define ST_INFILE 0x1 /* next token is the input file */
#define ST_OUTFILE 0x2 /* next token is the output file */ /* Global variables */
extern char **environ; /* defined in libc */
char prompt[] = "tsh> "; /* command line prompt (DO NOT CHANGE) */
int verbose = ; /* if true, print additional output */
int nextjid = ; /* next job ID to allocate */
char sbuf[MAXLINE]; /* for composing sprintf messages */ struct job_t { /* The job struct */
pid_t pid; /* job PID */
int jid; /* job ID [1, 2, ...] */
int state; /* UNDEF, BG, FG, or ST */
char cmdline[MAXLINE]; /* command line */
};
struct job_t job_list[MAXJOBS]; /* The job list */ struct cmdline_tokens {
int argc; /* Number of arguments */
char *argv[MAXARGS]; /* The arguments list */
char *infile; /* The input file */
char *outfile; /* The output file */
enum builtins_t { /* Indicates if argv[0] is a builtin command */
BUILTIN_NONE, BUILTIN_QUIT, BUILTIN_JOBS, BUILTIN_BG, BUILTIN_FG
} builtins;
};
/* End global variables */ /* Function prototypes */
void eval(char *cmdline); void sigchld_handler(int sig);
void sigtstp_handler(int sig);
void sigint_handler(int sig); /* Here are helper routines that we've provided for you */
int parseline(const char *cmdline, struct cmdline_tokens *tok);
void sigquit_handler(int sig); void clearjob(struct job_t *job);
void initjobs(struct job_t *job_list);
int maxjid(struct job_t *job_list);
int addjob(struct job_t *job_list, pid_t pid, int state, char *cmdline);
int deletejob(struct job_t *job_list, pid_t pid);
pid_t fgpid(struct job_t *job_list);
struct job_t *getjobpid(struct job_t *job_list, pid_t pid);
struct job_t *getjobjid(struct job_t *job_list, int jid);
int pid2jid(pid_t pid);
void listjobs(struct job_t *job_list, int output_fd); void usage(void);
void unix_error(char *msg);
void app_error(char *msg);
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler); /*
* main - The shell's main routine
*/
int main(int argc, char **argv) {
char c;
char cmdline[MAXLINE]; /* cmdline for fgets */
int emit_prompt = ; /* emit prompt (default) */ /* Redirect stderr to stdout (so that driver will get all output
* on the pipe connected to stdout) */
dup2(, ); /* Parse the command line */
while ((c = getopt(argc, argv, "hvp")) != EOF) {
switch (c) {
case 'h': /* print help message */
usage();
break;
case 'v': /* emit additional diagnostic info */
verbose = ;
break;
case 'p': /* don't print a prompt */
emit_prompt = ; /* handy for automatic testing */
break;
default:
usage();
}
} /* Install the signal handlers */ /* These are the ones you will need to implement */
Signal(SIGINT, sigint_handler); /* ctrl-c */
Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */
Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */
Signal(SIGTTIN, SIG_IGN);
Signal(SIGTTOU, SIG_IGN); /* This one provides a clean way to kill the shell */
Signal(SIGQUIT, sigquit_handler); /* Initialize the job list */
initjobs(job_list); /* Execute the shell's read/eval loop */
while () { if (emit_prompt) {
printf("%s", prompt);
fflush(stdout);
}
if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
app_error("fgets error");
if (feof(stdin)) {
/* End of file (ctrl-d) */
printf("\n");
fflush(stdout);
fflush(stderr);
exit();
} /* Remove the trailing newline */
cmdline[strlen(cmdline) - ] = '\0'; /* Evaluate the command line */
eval(cmdline); fflush(stdout);
fflush(stdout);
} exit(); /* control never reaches here */
} /*
* eval - Evaluate the command line that the user has just typed in
*
* If the user has requested a built-in command (quit, jobs, bg or fg)
* then execute it immediately. Otherwise, fork a child process and
* run the job in the context of the child. If the job is running in
* the foreground, wait for it to terminate and then return. Note:
* each child process must have a unique process group ID so that our
* background children don't receive SIGINT (SIGTSTP) from the kernel
* when we type ctrl-c (ctrl-z) at the keyboard.
*/
void eval(char *cmdline) {
int bg; /* should the job run in bg or fg? */
struct cmdline_tokens tok; /* Parse command line */
bg = parseline(cmdline, &tok); if (bg == -)
return; /* parsing error */
if (tok.argv[] == NULL)
return; /* ignore empty lines */ int stdi,stdo;
stdi=dup(STDIN_FILENO);
stdo=dup(STDOUT_FILENO); int infg, outfg;
infg = -;
outfg = -;
if (tok.infile != NULL) {
infg = open(tok.infile, O_RDONLY, );
dup2(infg, STDIN_FILENO);
}
if (tok.outfile != NULL) {
outfg = open(tok.outfile, O_RDWR, );
dup2(outfg, STDOUT_FILENO);
} pid_t pid;
struct job_t *job;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTSTP);
if (tok.builtins == BUILTIN_NONE) {
sigprocmask(SIG_BLOCK, &mask, NULL); if ((pid = fork()) == ) {
sigprocmask(SIG_UNBLOCK, &mask, NULL);
setpgid(, ); execve(tok.argv[], tok.argv, environ); if(infg!=-)
close(infg);
if(outfg!=-)
close(outfg); } else { addjob(job_list, pid, bg + , cmdline);
job = getjobpid(job_list, pid);
sigprocmask(SIG_UNBLOCK, &mask, NULL); sigemptyset(&mask);
if (!bg) {
while(pid==fgpid(job_list))
sleep();
} else {
printf("[%d] (%d) %s\n", job->jid, pid, job->cmdline);
}
} } else {
if(tok.builtins==BUILTIN_QUIT)
exit();
else if(tok.builtins==BUILTIN_JOBS) {
listjobs(job_list,STDOUT_FILENO);
}
else{
int jid;
if(tok.argv[][]=='%')
jid=atoi((tok.argv[])+sizeof(char));
else
jid=pid2jid(atoi(tok.argv[]));
job=getjobjid(job_list,jid);
if(tok.builtins==BUILTIN_BG) {
printf("[%d] (%d) %s\n",job->jid,job->pid,job->cmdline);
job->state=BG;
kill(-(job->pid),SIGCONT);
} else {
job->state=FG;
kill(-(job->pid),SIGCONT);
}
}
} dup2(stdi, STDIN_FILENO);
dup2(stdo, STDOUT_FILENO);
if(infg!=-)
close(infg);
if(outfg!=-)
close(outfg);
return;
} /*
* parseline - Parse the command line and build the argv array.
*
* Parameters:
* cmdline: The command line, in the form:
*
* command [arguments...] [< infile] [> oufile] [&]
*
* tok: Pointer to a cmdline_tokens structure. The elements of this
* structure will be populated with the parsed tokens. Characters
* enclosed in single or double quotes are treated as a single
* argument.
* Returns:
* 1: if the user has requested a BG job
* 0: if the user has requested a FG job
* -1: if cmdline is incorrectly formatted
*
* Note: The string elements of tok (e.g., argv[], infile, outfile)
* are statically allocated inside parseline() and will be
* overwritten the next time this function is invoked.
*/
int parseline(const char *cmdline, struct cmdline_tokens *tok) { static char array[MAXLINE]; /* holds local copy of command line */
const char delims[] = " \t\r\n"; /* argument delimiters (white-space) */
char *buf = array; /* ptr that traverses command line */
char *next; /* ptr to the end of the current arg */
char *endbuf; /* ptr to the end of the cmdline string */
int is_bg; /* background job? */ int parsing_state; /* indicates if the next token is the
input or output file */ if (cmdline == NULL) {
(void) fprintf(stderr, "Error: command line is NULL\n");
return -;
} (void) strncpy(buf, cmdline, MAXLINE);
endbuf = buf + strlen(buf); tok->infile = NULL;
tok->outfile = NULL; /* Build the argv list */
parsing_state = ST_NORMAL;
tok->argc = ; while (buf < endbuf) {
/* Skip the white-spaces */
buf += strspn(buf, delims);
if (buf >= endbuf)
break; /* Check for I/O redirection specifiers */
if (*buf == '<') {
if (tok->infile) {
(void) fprintf(stderr, "Error: Ambiguous I/O redirection\n");
return -;
}
parsing_state |= ST_INFILE;
buf++;
continue;
}
if (*buf == '>') {
if (tok->outfile) {
(void) fprintf(stderr, "Error: Ambiguous I/O redirection\n");
return -;
}
parsing_state |= ST_OUTFILE;
buf++;
continue;
} if (*buf == '\'' || *buf == '\"') {
/* Detect quoted tokens */
buf++;
next = strchr(buf, *(buf - ));
} else {
/* Find next delimiter */
next = buf + strcspn(buf, delims);
} if (next == NULL) {
/* Returned by strchr(); this means that the closing
quote was not found. */
(void) fprintf(stderr, "Error: unmatched %c.\n", *(buf - ));
return -;
} /* Terminate the token */
*next = '\0'; /* Record the token as either the next argument or the input/output file */
switch (parsing_state) {
case ST_NORMAL:
tok->argv[tok->argc++] = buf;
break;
case ST_INFILE:
tok->infile = buf;
break;
case ST_OUTFILE:
tok->outfile = buf;
break;
default:
(void) fprintf(stderr, "Error: Ambiguous I/O redirection\n");
return -;
}
parsing_state = ST_NORMAL; /* Check if argv is full */
if (tok->argc >= MAXARGS - )
break; buf = next + ;
} if (parsing_state != ST_NORMAL) {
(void) fprintf(stderr,
"Error: must provide file name for redirection\n");
return -;
} /* The argument list must end with a NULL pointer */
tok->argv[tok->argc] = NULL; if (tok->argc == ) /* ignore blank line */
return ; if (!strcmp(tok->argv[], "quit")) { /* quit command */
tok->builtins = BUILTIN_QUIT;
} else if (!strcmp(tok->argv[], "jobs")) { /* jobs command */
tok->builtins = BUILTIN_JOBS;
} else if (!strcmp(tok->argv[], "bg")) { /* bg command */
tok->builtins = BUILTIN_BG;
} else if (!strcmp(tok->argv[], "fg")) { /* fg command */
tok->builtins = BUILTIN_FG;
} else {
tok->builtins = BUILTIN_NONE;
} /* Should the job run in the background? */
if ((is_bg = (*tok->argv[tok->argc - ] == '&')) != )
tok->argv[--tok->argc] = NULL; return is_bg;
} /*****************
* Signal handlers
*****************/ /*
* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
* a child job terminates (becomes a zombie), or stops because it
* received a SIGSTOP, SIGTSTP, SIGTTIN or SIGTTOU signal. The
* handler reaps all available zombie children, but doesn't wait
* for any other currently running children to terminate.
*/
void sigchld_handler(int sig) {
pid_t pid;
int status;
while ((pid = waitpid(-, &status, WUNTRACED | WNOHANG)) > ) {
if (WIFSTOPPED(status)) {
int jid=pid2jid(pid);
if(jid!=) {
printf("Job [%d] (%d) stopped by signal %d\n",jid,pid,WSTOPSIG(status));
(getjobpid(job_list,pid))->state=ST;
}
} else if (WIFSIGNALED(status)) {
if (WTERMSIG(status) == SIGINT) {
int jid=pid2jid(pid);
if(jid!=) {
printf("Job [%d] (%d) terminated by signal %d\n",jid,pid,SIGINT);
deletejob(job_list,pid);
}
}
else
deletejob(job_list, pid);
} else
deletejob(job_list, pid);
}
return;
} /*
* sigint_handler - The kernel sends a SIGINT to the shell whenver the
* user types ctrl-c at the keyboard. Catch it and send it along
* to the foreground job.
*/
void sigint_handler(int sig) {
pid_t pid = fgpid(job_list);
if (pid != ) {
kill(-pid, sig);
}
return;
} /*
* sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
* the user types ctrl-z at the keyboard. Catch it and suspend the
* foreground job by sending it a SIGTSTP.
*/
void sigtstp_handler(int sig) {
pid_t pid = fgpid(job_list);
if (pid != ) {
kill(-pid, sig);
}
return;
} /*********************
* End signal handlers
*********************/ /***********************************************
* Helper routines that manipulate the job list
**********************************************/ /* clearjob - Clear the entries in a job struct */
void clearjob(struct job_t *job) {
job->pid = ;
job->jid = ;
job->state = UNDEF;
job->cmdline[] = '\0';
} /* initjobs - Initialize the job list */
void initjobs(struct job_t *job_list) {
int i; for (i = ; i < MAXJOBS; i++)
clearjob(&job_list[i]);
} /* maxjid - Returns largest allocated job ID */
int maxjid(struct job_t *job_list) {
int i, max = ; for (i = ; i < MAXJOBS; i++)
if (job_list[i].jid > max)
max = job_list[i].jid;
return max;
} /* addjob - Add a job to the job list */
int addjob(struct job_t *job_list, pid_t pid, int state, char *cmdline) {
int i; if (pid < )
return ; for (i = ; i < MAXJOBS; i++) {
if (job_list[i].pid == ) {
job_list[i].pid = pid;
job_list[i].state = state;
job_list[i].jid = nextjid++;
if (nextjid > MAXJOBS)
nextjid = ;
strcpy(job_list[i].cmdline, cmdline);
if (verbose) {
printf("Added job [%d] %d %s\n", job_list[i].jid,
job_list[i].pid, job_list[i].cmdline);
}
return ;
}
}
printf("Tried to create too many jobs\n");
return ;
} /* deletejob - Delete a job whose PID=pid from the job list */
int deletejob(struct job_t *job_list, pid_t pid) {
int i; if (pid < )
return ; for (i = ; i < MAXJOBS; i++) {
if (job_list[i].pid == pid) {
clearjob(&job_list[i]);
nextjid = maxjid(job_list) + ;
return ;
}
}
return ;
} /* fgpid - Return PID of current foreground job, 0 if no such job */
pid_t fgpid(struct job_t *job_list) {
int i; for (i = ; i < MAXJOBS; i++)
if (job_list[i].state == FG)
return job_list[i].pid;
return ;
} /* getjobpid - Find a job (by PID) on the job list */
struct job_t *getjobpid(struct job_t *job_list, pid_t pid) {
int i; if (pid < )
return NULL;
for (i = ; i < MAXJOBS; i++)
if (job_list[i].pid == pid)
return &job_list[i];
return NULL;
} /* getjobjid - Find a job (by JID) on the job list */
struct job_t *getjobjid(struct job_t *job_list, int jid) {
int i; if (jid < )
return NULL;
for (i = ; i < MAXJOBS; i++)
if (job_list[i].jid == jid)
return &job_list[i];
return NULL;
} /* pid2jid - Map process ID to job ID */
int pid2jid(pid_t pid) {
int i; if (pid < )
return ;
for (i = ; i < MAXJOBS; i++)
if (job_list[i].pid == pid) {
return job_list[i].jid;
}
return ;
} /* listjobs - Print the job list */
void listjobs(struct job_t *job_list, int output_fd) {
int i;
char buf[MAXLINE]; for (i = ; i < MAXJOBS; i++) {
memset(buf, '\0', MAXLINE);
if (job_list[i].pid != ) {
sprintf(buf, "[%d] (%d) ", job_list[i].jid, job_list[i].pid);
if (write(output_fd, buf, strlen(buf)) < ) {
fprintf(stderr, "Error writing to output file\n");
exit();
}
memset(buf, '\0', MAXLINE);
switch (job_list[i].state) {
case BG:
sprintf(buf, "Running ");
break;
case FG:
sprintf(buf, "Foreground ");
break;
case ST:
sprintf(buf, "Stopped ");
break;
default:
sprintf(buf, "listjobs: Internal error: job[%d].state=%d ", i,
job_list[i].state);
}
if (write(output_fd, buf, strlen(buf)) < ) {
fprintf(stderr, "Error writing to output file\n");
exit();
}
memset(buf, '\0', MAXLINE);
sprintf(buf, "%s\n", job_list[i].cmdline);
if (write(output_fd, buf, strlen(buf)) < ) {
fprintf(stderr, "Error writing to output file\n");
exit();
}
}
}
if (output_fd != STDOUT_FILENO)
close(output_fd);
}
/******************************
* end job list helper routines
******************************/ /***********************
* Other helper routines
***********************/ /*
* usage - print a help message
*/
void usage(void) {
printf("Usage: shell [-hvp]\n");
printf(" -h print this message\n");
printf(" -v print additional diagnostic information\n");
printf(" -p do not emit a command prompt\n");
exit();
} /*
* unix_error - unix-style error routine
*/
void unix_error(char *msg) {
fprintf(stdout, "%s: %s\n", msg, strerror(errno));
exit();
} /*
* app_error - application-style error routine
*/
void app_error(char *msg) {
fprintf(stdout, "%s\n", msg);
exit();
} /*
* Signal - wrapper for the sigaction function
*/
handler_t *Signal(int signum, handler_t *handler) {
struct sigaction action, old_action; action.sa_handler = handler;
sigemptyset(&action.sa_mask); /* block sigs of type being handled */
action.sa_flags = SA_RESTART; /* restart syscalls if possible */ if (sigaction(signum, &action, &old_action) < )
unix_error("Signal error");
return (old_action.sa_handler);
} /*
* sigquit_handler - The driver program can gracefully terminate the
* child shell by sending it a SIGQUIT signal.
*/
void sigquit_handler(int sig) {
printf("Terminating after receipt of SIGQUIT signal\n");
exit();
}

CSAPP2e:Shell lab 解答的更多相关文章

  1. CSAPP shell Lab 详细解答

    Shell Lab的任务为实现一个带有作业控制的简单Shell,需要对异常控制流特别是信号有比较好的理解才能完成.需要详细阅读CS:APP第八章异常控制流并理解所有例程. Slides下载:https ...

  2. 深入理解计算机系统项目之 Shell Lab

    博客中的文章均为meelo原创,请务必以链接形式注明本文地址 Shell Lab是CMU计算机系统入门课程的一个实验.在这个实验里你需要实现一个shell,shell是用户与计算机的交互界面.普通意义 ...

  3. 【CSAPP】Shell Lab 实验笔记

    shlab这节是要求写个支持任务(job)功能的简易shell,主要考察了linux信号机制的相关内容.难度上如果熟读了<CSAPP>的"异常控制流"一章,应该是可以不 ...

  4. Spider-Scrapy css选择器提取数据

    首先我们来说说css选择器:其实在上面的概述:和scrapy相关的函数就这么三个而已:response.css("css表达式").extract().extract_first( ...

  5. CS基础课不完全自学指南

    本文讲的是计算机学生怎么自学专业课,说长点就是该如何借助网络上已有的高质量学习资源(主要是公开课)来系统性的来点亮自己的CS技能树.这篇文章完全就是一篇自学性质的指南,需要对编程充满热情,起码觉得编程 ...

  6. RH033读书笔记(5)-Lab 6 Exploring the Bash Shell

    Lab 6 Exploring the Bash Shell Sequence 1: Directory and file organization 1. Log in as user student ...

  7. RH033读书笔记(11)-Lab 12 Configuring the bash Shell

    Sequence 1: Configuring the bash Shell Deliverable: A system with new aliases that clear the screen, ...

  8. 企业shell面试题及解答

    1.面试题:使用for循环在/tmp目录下批量创建10个html文件,其中每个文件需要包含10个随机小写字母加固定字符串template,示例如下 aaesdffbnv_template.html 方 ...

  9. Lab: Web shell upload via Content-Type restriction bypass

    首先上传一个正常头像. 之后,上传木马文件,并抓包 POST /my-account/avatar HTTP/1.1 Host: ac4f1f7d1eaa6cd2c0d80622001b00f9.we ...

随机推荐

  1. Fragment初步了解

    fragment 1.fragment解释: 从英文上来说fragment是碎片和片段的意思,这解释的是相当到位的,因为android中的fragment就像是碎片嵌在了Activity当中的,为构造 ...

  2. Array--Good parts

    js数组没有上届 --如果你用大于或等于当前length的数字作为下标来存储一个元素,那么length会被增大以容纳新元素,不会发生数组越界. 数组也是对象 --可以添加属性.a["name ...

  3. UDP套接口编程

    常用的UDP实现的程序:DNS域名系统,NFS网络文件系统,SNMP简单网络管理协议 ssize_t recvfrom(int sockfd,void *buff,size_t nbytes,int ...

  4. maven中如何打包源代码

    http://yanghaoyuan.iteye.com/blog/2032406 使用Maven对项目部署太方便了,特别是依赖关系,最近学习使用Maven,为了备忘和技术的分享特意注册个账号记录到博 ...

  5. 解决从内部存储设备安装apk提示Permission Denied

    做应用商店,下载apk,考虑一种情况,如果没有sd卡的情况下就将apk下载到 Internal Cache目录下. 下载都正常,但是在安装的时候提示Permission Denied /data/da ...

  6. 【ASP.NET】C# 将HTML中Table导出到Excel(TableToExcel)

    首先,说下应用场景 就是,把页面呈现的Table 导出到Excel中.其中使用的原理是 前台使用ajax调用aspx后台,传递过去参数值,导出.使用的组件是NPOI. 前台调用: <script ...

  7. Java并发学习之十九——线程同步工具之Phaser

    本文是学习网络上的文章时的总结.感谢大家无私的分享. JDK 1.7 加入了一个新的工具Phaser.Phaser的在功能上与CountDownLatch有部分重合. 以下使用Phaser类来同步3个 ...

  8. JS可以做什么,它的能力范围 View----------Request/Submit------------------Server

    View----------Request/Submit------------------Server javascript--------><script>标签方式(页面,动态插 ...

  9. Android短信监听软件

    本案例是在android手机中运行,是一个没有界面的短信监听软件.主要是用BroadcastReceiver来接受短信广播,当接收到短信后就跳转到service中来转发短信.哈哈,不是用来干坏事的.这 ...

  10. ubuntu搭建LAMP服务器

    新手记录下...... 安装apache apt-get install apache2 安装mysql apt-get install mysql-server 安装php apt-get inst ...