系统调用, 产生软中断,从用户级别切换到特权级别, 程序跳转到中断处理函数; 分级管理, 内核态由操作系统管理,有利于安全控制;
程序是磁盘上的文件, 进程是内存里运行的程序;
程序的各段功能:
代码段: 函数代码和变量;
数据段: 全局变量, 静态变量;
匿名anon空间: 堆空间, malloc使用的空间;
stack栈: 局部变量;
操作系统实际就是一个大的中断处理函数, 主要做的事情:
分配内存, 建立VA-PA转换表, 处理错误;
mmu主要功能是地址转换和内存保护;
PCB中的信息有:
task_struct, mm_struct, pid, fd table, process_time, context, exit status, signals
程序真正的入口是_start, 进行必要的准备后调用main函数;
int main(int argc, char *argv[]) 当中,argv的最后一项为NULL;
进程:
#include
void exit (int status); ANSI_C定义的,会关闭文件然后退出;
#include
void _exit(int status); POSIX1.1定义的,直接退出;
exit(n) 等同于 return(n); n为指定的exit status;
exit status未定义的情况:
调用exit, _exit不带参数; main执行无返回值的return; main执行隐式返回, 无exit 或 return;
全局变量environ指向环境表, 使用方法如下:
#include
#include
extern char **environ;
int main()
{
while (*environ)
printf ("%s\n", *environ++);
}
环境变量操作函数:
#include
char *getenv(const char *name); 返回指向与name关联的value的指针, 未找到为NULL;
int putenv(const char *str); 返回成功为0,出错为非0; 参数形式:"name=value"; 若name存在,删除原来定义;
int setenv(const char *name, const char *value, int rewrite); 返回成功为0,出错为非0;
将环境变量name的值设置为value,若已经存在name, 若rewrite为0, 不删除原定义;若为非0, 删除原定义;
void unsetenv(const char *name); 删除name的定义,不存在name也不出错;
进程控制函数:
#include
#include
pid_t fork(void); 返回值子进程中为0,父进程中为子进程ID, 出错为-1;
fork失败的两个主要原因: 1.系统中有太多进程; 2.该用户ID的进程总数超过了系统限制;
#include
#include
pid_t wait(int *statloc); 调用者分配的内存, 若statloc不是空指针, 终止状态存在此处;
pid_t waitpid(pid_t pid, int *statloc, int options);
返回值, 成功为子进程的PID, 出错为-1;
option选项有: WNOHANG WUNTRACED WCONTINUED
终止状态使用定义在中的宏 WEXITSTATUS(status) 来读取;
关于退出状态: WEXITSTATUS 可用命令grep 'WEXITSTATUS' /usr/include -nrs查看;
#define _WEXITSTATUS(status) (((status) & 0xff00) >> 8) define可用来代替一个简单函数;
wait和waitpid的区别: wait在子进程终止前一直阻塞, 它等待第一个终止的子进程; 而waitpid可以控制阻塞或不阻塞, 并且等待的进程也可控制;
父进程fork出的子进程, 当子进程终止时, 父进程可以用wait或waitpid取得终止状态(若是正常退出,则取得exit函数的参数值,如果异常退出,由内核产生一个指示退出原因的状态值);
如果父进程在子进程之前终止,则他的子进程全部由init进程领养,reparent;
如果子进程在父进程之前终止,内核会为终止的子进程保持一定量的信息, 至少包括进程ID, 终止状态, 使用的CPU时间等信息, 父进程可用wait或waitpid取得, 若父进程(仍然在运行)不作处理, 则产生僵死进程zombie, 资源已经释放了, 但PCB还在, ps查看的状态为Z;
当一进程正常或异常终止时, 内核就向其父进程发出SIGCHLD信号. 系统的默认动作是忽略它;
wait或waitpid可能出现阻塞, 带子进程(已经终止)终止状态立即返回, 出错立即返回, 这么三种状态;
exec函数, 进程中每个打开的文件都有一个close-on-exec标志, 若有效则exec后关闭该描述符, 系统默认是无效;
#include
int execl(const char *pathname, const char * arg0, ...);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ...);
int execve(const char *pathname, char *const argv[], char * const envp[]);
int execlp(const char *filename, const char *arg0, ...);
int execvp(const char *filename, char *const argv[]);
若出错返回-1, 成功则不返回;
pathname是路径名, filename则分两种情况,1. filename中包含/, 则将其视为路径名,否则, 2. 按照PATH环境变量,搜寻可执行文件; 若文件是一个脚本文件, 则实际执行的程序是脚本解释器, 而不是脚本.
名称带l的, 表示参数传递是list方式, 并以空指针结尾;
名称带v的, 则应先构造一个指向各参数的指针数组作为参数;
名称带p的, 用PATH环境变量寻找可执行文件filename;
名称带e的, 表示该函数取envp[]数组, 而不是使用当前环境;
使用举例:
#include
char *const ps_argv[] = { "ps", "ax", 0};
char *const ps_envp[] = { "PATH=/bin:/usr/bin", "TERM=console", 0};
/*possible calls to exec function*/
execl("/bin/ps", "ps", "ax", 0);
execv("/bin/ps", ps_argv);
execle("/bin/ps", "ps", "ax", 0, ps_envp);
execve("/bin/ps", ps_argv, ps_envp);
execlp("ps", "ps", "ax", 0);
execvp("ps", ps_argv);
#include
int system(const char *cmdstring);
? 如果cmdstring是空指针, 若shell程序不可用时,返回0; 可用于判断本地系统是否支持system函数;
返回值:
1. fork失败或waitpid返回除EINTR之外的错误, 返回-1, errno中设置了错误类型;
2. exec失败, 表示不能执行shell, 返回exit(127);
3. 三个函数(fork, exec, waitpid)都成功, 返回是shell的终止状态, 格式与waitpid的statloc同;
管道:
pipe是UNIX IPC (InterProcess Communication)的一种古老机制, 它有两种限制:
1. 半双工, 数据只能在一个方向上流动;
2. 只能在具有公共祖先的进程间使用.如父 , 子进程之间使用;
#include
int pipe(int filedes[2]);
成功返回0, 出错返回-1; filedes参数返回2个文件描述符, fd[0]从管道读, fd[1]写入管道;
当管道的一端被关闭后, 下列规则起作用:
当读一个写端被关闭的管道时, 所有数据被读取后, read返回0, 表示结束;
当写一个读端被关闭的管道时, 产生信号SIGPIPE, write出错返回, errno设置为EPIPE.
写管道时, PIPE_BUF定义了内核中管道缓存的大小, 也就是一次性写的长度, 原子的写操作;
FIFO是命名管道, 不相关的进程也能交换数据. 以一种特殊的文件形式存在文件系统中, 文件类型为p;
#include
#include
int mkfifo(const char *pathname, mode_t mode); 创建一个FIFO文件, 若成功返回0, 出错返回-1;
mode参数含义与open函数中的mode相同;
创建之后, 其他进程只要知道这个文件的pathname, 就可以对这个文件进行读写来通信; FIFO在磁盘上只存储一个inode而没有数据块, 数据通过内核的缓冲区来传递;
mkfifo命令可以创建一个FIFO文件, 然后用重定向可以对其存取;
FIFO现在应用较少, 现在广泛使用的是UNIX Domain socket通信机制.
IPC通信机制汇总:
1. fork或exec, 父进程将已经打开的描述符传给子进程;
2. UNIX Domain socket, 应用较广泛;
3. 管道;
4 FIFO;
5. 几个进程读写某个共享文件, 可通过文件加锁来同步;
6. 进程间发信号, 一般使用SIGUSR1, SIGUSR2实现用户自定义功能;
7. mmap函数, 几个进程映射同一内存区域进行通信;
8. SYS V IPC, 消息队列, 信号量, 共享内存, 现在基本不用;
一些例程:
/*wait.c*/
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
char *message;
int n;
int exit_code;
printf("fork program starting\n");
pid = fork();
switch (pid)
{
case -1:
perror("fork failed");
exit(1);
case 0:
message = "This is the child";
n = 5;
exit_code = 37;
break;
default:
message = "This is the parent";
n = 3;
exit_code = 0;
break;
}
for(; n > 0; n--)
{
puts(message);
sleep(1);
}
if (pid != 0)
{
int stat_val;
pid_t child_pid;
child_pid = wait(&stat_val);
printf("Child has finished: PID = %d\n", child_pid);
if (WIFEXITED(stat_val))
printf("Child exited with code 0X%x\n",
stat_val);
else
printf("Child terminated abnormally\n");
}
exit(exit_code);
}
/*pipedemo.c 实验用管道实现进程间通信
* pipe得到的文件描述符通过exec参数传递,
* 或通过把pipe得到的描述符复制保存在STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO中, 再执行exec
*子进程中不能直接用pipe得到的描述符吗? 在执行exec前, 子进程可以使用.
*/
#include
#include
#include
#include
int main()
{
int data_processed;
int file_pipes[2];
const char some_data[] = "123";
char buffer[BUFSIZ + 1];
pid_t fork_result;
memset(buffer, '\0', sizeof(buffer));
if (pipe(file_pipes) == 0)
{
fork_result = fork();
if (fork_result == (pid_t)-1)
{
fprintf(stderr, "Fork failure");
exit(EXIT_FAILURE);
}
if (fork_result == 0)
{
sprintf(buffer, "%d", file_pipes[0]); //把数字转字符串
(void)execl("pipe4", "pipe4", buffer, (char *)0);
exit(EXIT_FAILURE);
}
else
{
data_processed = write(file_pipes[1], some_data,
strlen(some_data));
wait();
printf ("%d - wrote %d bytes\n", getpid(),
data_processed);
}
}
exit(EXIT_SUCCESS);
}
/*pipe4.c*/
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int data_processed;
char buffer[BUFSIZ + 1];
int file_descriptor;
memset(buffer, '\0', sizeof(buffer));
sscanf(argv[1], "%d", &file_descriptor); //把字符串转数字
data_processed = read(file_descriptor, buffer, BUFSIZ);
printf("%d - read %d bytes: %s\n", getpid(), data_processed,
buffer);
exit(EXIT_SUCCESS);
}
/*upper.c 包转程序调用upper程序,并不具体实现*/
#include
#include
#include
int main(int argc, char *argv[])
{
int ch;
printf ("%s\n", argv[0]);
while ((ch = getchar()) != EOF)
{
putchar(toupper(ch));
}
exit(0);
}
/*wrapper.c*/
#include
#include
#include
#include
int main(int argc, char *argv[])
{
char *filename;
int in;
if (argc != 2)
{
fprintf(stderr, "usage: wrapper file\n");
exit(1);
}
filename = argv[1];
in = open(filename, O_RDONLY);
dup2(in, STDIN_FILENO);
execl("./upper", "per", 0);
perror("could not exec ./upper");
exit(3);
}
/*toyshell.c 一个简单的shell程序*/
#include
#include
#include
#include
#include
#include
#define N 5 // the max option
char *cporder1[N];
char *cporder2[N];
char rdfile[30];
char wrfile[30];
char buf[100];
int getorder(char *cporder[], char *buf)
{
char *cp = buf;
int i, j, state;
for (i = 0; i < N; i++)
{
state = 0;
while (*cp == ' '|| *cp == '\t')
cp++;
if (*cp != ' ' && *cp != '\t' && *cp != '\n'
&& *cp != '<' && *cp != '>')
state = 1, cporder[i] = cp;
while (*cp != ' ' && *cp != '\t' && *cp != '\n'
&& *cp != '<' && *cp != '>')
cp++;
if (*cp == '\n' || *cp == '<' || *cp == '>')
{
if (*cp == '<')
{
*cp = '\0';
cp++;
while (*cp == ' ' || *cp == '\t')
cp++;
for (j = 0; *cp != ' ' && *cp != '\t'
&& *cp != '\n' && *cp != '>';j++)
rdfile[j] = *cp++;
}
if (*cp == '>')
{
*cp = '\0';
cp++;
while (*cp == ' ' || *cp == '\t')
cp++;
for (j = 0; *cp != ' ' && *cp != '\t'
&& *cp != '\n' && *cp != '<';j++)
wrfile[j] = *cp++;
}
if (*cp == '\n')
{
if (0 == state)
{
*cp = '\0';
cporder[i] = NULL;
break;
}
}
}
else
*cp++ = '\0';
}
return i;
}
int main()
{
char *cp;
char *splitcp, *sbuf1, *sbuf2;
int pid, n;
int fd1, fd2;
int file_pipes[2];
pid_t fork_result, fork_result1;
printf ("hello, you are welcome!\n");
while (1)
{
//printf ("\n");
printf ("akae@root# ");
memset (buf, '\0', sizeof(buf));
memset (rdfile, '\0', sizeof(rdfile));
memset (wrfile, '\0', sizeof(wrfile));
fd1 = fd2 = -1;
if (fgets(buf, 50, stdin))
{
if (strcmp(buf, "quit\n") == 0)
printf ("thank you, byebye!!!\n"), exit(0);
if ((splitcp = strstr(buf, "|")) != NULL)
{
*splitcp++ = '\n';
sbuf1 = buf;
sbuf2 = splitcp;
getorder(cporder1, sbuf1);
getorder(cporder2, sbuf2);
if (pipe(file_pipes) == 0)
{
fork_result = fork();
if (fork_result == (pid_t)-1)
{
fprintf(stderr, "Fork failure");
exit(EXIT_FAILURE);
}
if (fork_result == 0)
{
if (*wrfile != '\0')
{
fd2 = open(wrfile, O_WRONLY |
O_CREAT,S_IRUSR | S_IWUSR);
if (fd2 == -1)
printf("Cannot open %s\n",wrfile);
else
dup2(fd2, STDOUT_FILENO);
}
close(file_pipes[1]);
dup2(file_pipes[0], STDIN_FILENO);
execvp(cporder2[0], cporder2);
exit(EXIT_FAILURE);
}
else
{
fork_result1 = fork();
if (fork_result1 == (pid_t)-1)
{
fprintf(stderr, "Fork failure");
exit(EXIT_FAILURE);
}
if (fork_result1 == 0)
{
if (*rdfile != '\0')
{
fd1 = open(rdfile, O_RDWR);
if (fd1 == -1)
printf("Cannot open %s\n",rdfile);
else
dup2(fd1, STDIN_FILENO);
}
close (file_pipes[0]);
dup2(file_pipes[1], STDOUT_FILENO);
execvp(cporder1[0],cporder1);
exit(EXIT_FAILURE);
}
else
{
close(file_pipes[0]);
close(file_pipes[1]);
waitpid(fork_result, NULL, 0);
waitpid(fork_result1, NULL, 0);
}
}
}
}
else
{
n = getorder(cporder1, buf);
pid = fork();
switch (pid)
{
case -1:
perror("fork failed");
exit(1);
break;
case 0:
if (*rdfile != '\0')
{
fd1 = open(rdfile, O_RDWR);
if (fd1 == -1)
printf("Cannot open %s\n",rdfile);
else
dup2(fd1, STDIN_FILENO);
}
if (*wrfile != '\0')
{
fd2 = open(wrfile, O_WRONLY |
O_CREAT,S_IRUSR | S_IWUSR);
if (fd2 == -1)
printf("Cannot open %s\n",wrfile);
else
dup2(fd2, STDOUT_FILENO);
}
execvp (cporder1[0], cporder1);
exit(EXIT_FAILURE);
break;
default:
wait(NULL);
break;
}
}
}
}
}
点滴信息:
nm a.out 查看符号表
虚拟地址是编译时决定的, 内核地址占1G;
man bash-buildins查看内建命令
0进程启动内核, 1进程启动;
$echo $? 可以获取上一命令的退出状态;
阅读(1158) | 评论(0) | 转发(0) |