- 浏览: 2002680 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (651)
- ACE (35)
- BAT (9)
- C/C++ (116)
- fast-cgi (14)
- COM (27)
- python (59)
- CGI (4)
- C# (2)
- VC (84)
- DataBase (29)
- Linux (96)
- P2P (6)
- PHP (15)
- Web (6)
- Memcached (7)
- IME输入法 (11)
- 设计模式 (2)
- 搜索引擎 (1)
- 个人情感 (4)
- 笔试/面试 (3)
- 一亩三分地 (33)
- 历史 (2)
- 地理 (1)
- 人物 (3)
- 经济 (0)
- 不仅仅是笑哦 (43)
- 小故事大道理 (2)
- http://www.bjdsmyysjk120.com/ (0)
- http://www.bjdsmyy120.com/ (0)
- 它山之石可以攻玉 (15)
- 大学生你关注些什么 (28)
- 数据恢复 (1)
最新评论
-
luokaichuang:
这个规范里还是没有让我明白当浏览器上传文件时,STDIN的消息 ...
FastCGI规范 -
effort_fan:
好文章!学习了,谢谢分享!
com技术简介 -
vcell:
有错误os.walk(strPath)返回的已经是全部的文件和 ...
通过python获取目录的大小 -
feifeigd:
feifeigd 写道注意:文章中的CPP示例第二行 #inc ...
ATL入门:利用ATL编写简单的COM组件 -
feifeigd:
注意:文章中的CPP示例第二行 #include " ...
ATL入门:利用ATL编写简单的COM组件
Linux下的C编程实战之(三)
1.Linux进程
Linux进程在内存中包含三部分数据:代码 段、堆栈段和数据段。代码段存放了程序的代码。代码段可以为机器中运行同一程序的数个进程共享。堆栈段存放的是子程序(函数)的返回地址、子程序的参数及
程序的局部变量。而数据段则存放程序的全局变量、常数以及动态数据分配的数据空间(比如用malloc函数申请的内存)。与代码段不同,如果系统中同时运
行多个相同的程序,它们不能使用同一堆栈段和数据段。
Linux进程主要有如下几种状态:用户状态(进程在用户状态下运行的状态)、 内核状态(进程在内核状态下运行的状态)、内存中就绪(进程没有执行,但处于就绪状态,只要内核调度它,就可以执行)、内存中睡眠(进程正在睡眠并且处于
内存中,没有被交换到SWAP设备)、就绪且换出(进程处于就绪状态,但是必须把它换入内存,内核才能再次调度它进行运行)、睡眠且换出(进程正在睡眠,
且被换出内存)、被抢先(进程从内核状态返回用户状态时,内核抢先于它,做了上下文切换,调度了另一个进程,原先这个进程就处于被抢先状态)、创建状态 (进程刚被创建,该进程存在,但既不是就绪状态,也不是睡眠状态,这个状态是除了进程0以外的所有进程的最初状态)、僵死状态(进程调用exit结束,进 程不再存在,但在进程表项中仍有记录,该记录可由父进程收集)。
下面我们来以一个进程从创建到消亡的过程讲解Linux进程状态转换的“生死因果”。
(1)进程被父进程通过系统调用fork创建而处于创建态;
(2)fork调用为子进程配置好内核数据结构和子进程私有数据结构后,子进程进入就绪态(或者在内存中就绪,或者因为内存不够而在SWAP设备中就绪);
(3)若进程在内存中就绪,进程可以被内核调度程序调度到CPU运行;
(4)内核调度该进程进入内核状态,再由内核状态返回用户状态执行。该进程在用户状态运行一定时间后,又会被调度程序所调度而进入内核状态,由此转入就
绪态。有时进程在用户状态运行时,也会因为需要内核服务,使用系统调用而进入内核状态,服务完毕,会由内核状态转回用户状态。要注意的是,进程在从内核状 态向用户状态返回时可能被抢占,这是由于有优先级更高的进程急需使用CPU,不能等到下一次调度时机,从而造成抢占;
(5)进程执行exit调用,进入僵死状态,最终结束。
2.进程控制
进程控制中主要涉及到进程的创建、睡眠和退出等,在Linux中主要提供了fork、exec、clone的进程创建方法,sleep的进程睡眠和exit的进程退出调用,另外Linux还提供了父进程等待子进程结束的系统调用wait。
fork
对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一,它执行一次却返回两个值,完全“不可思议”。先看下面的程序:
int main() { int i; if (fork() == 0) { for (i = 1; i < 3; i++) printf("This is child process\n"); } else { for (i = 1; i < 3; i++) printf("This is parent process\n"); } }
执行结果为:
This is child process
This is child process
This is parent process
This is parent process
fork在英文中是“分叉”的意思,这个名字取得很形象。一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就“分叉”了。当前进程 为父进程,通过fork()会产生一个子进程。对于父进程,fork函数返回子程序的进程号而对于子程序,fork函数则返回零,这就是一个函数返回两次
的本质。可以说,fork函数是Unix系统最杰出的成就之一,它是七十年代Unix早期的开发者经过理论和实践上的长期艰苦探索后取得的成果。
如果我们把上述程序中的循环放的大一点:
int main() { int i; if (fork() == 0) { for (i = 1; i < 10000; i++) printf("This is child process\n"); } else { for (i = 1; i < 10000; i++) printf("This is parent process\n"); } }
则可以明显地看到父进程和子进程的并发执行,交替地输出“This is
child process”和“This is parent process”。
此时此刻,我们还没有完全理解fork()函数,再来看下面的一段程序,看看究竟会产生多少个进程,程序的输出是什么?
int main() { int i; for (i = 0; i < 2; i++) { if (fork() == 0) { printf("This is child process\n"); } else { printf("This is parent process\n"); } } }
exec
在Linux中可使用exec函数族,包含多个函数(execl、execlp、execle、execv、execve和execvp),被用于启动一个指定路径和文件名的进程。
exec函数族的特点体现在:某进程一旦调用了exec类函数,正在执行的程序就被干掉了,系统把代码段替换成新的程序(由exec类函数执行)的代 码,并且原有的数据段和堆栈段也被废弃,新的数据段与堆栈段被分配,但是进程号却被保留。也就是说,exec执行的结果为:系统认为正在执行的还是原先的 进程,但是进程对应的程序被替换了。
fork函数可以创建一个子进程而当前进程不死,如果我们在fork的子进程中调用exec函数 族就可以实现既让父进程的代码执行又启动一个新的指定进程,这实在是很妙的。fork和exec的搭配巧妙地解决了程序启动另一程序的执行但自己仍继续运 行的问题,请看下面的例子:
char command[MAX_CMD_LEN]; void main() { int rtn; /* 子进程的返回数值 */ while (1) { /* 从终端读取要执行的命令 */ printf(">"); fgets(command, MAX_CMD_LEN, stdin); command[strlen(command) - 1] = 0; if (fork() == 0) { /* 子进程执行此命令 */ execlp(command, command); /* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/ perror(command); exit(errorno); } else { /* 父进程,等待子进程结束,并打印子进程的返回值 */ wait(&rtn); printf(" child process return %d\n", rtn); } } }
这个函数基本上实现了一个shell的功能,它读取用户输入的进程名和参数,并启动对应的进程。
clone
clone是Linux2.0以后才具备的新功能,它较fork更强(可认为fork是clone要实现的一部分),可以使得创建的子进程共享父进程的资源,并且要使用此函数必须在编译内核时设置clone_actually_works_ok选项。
clone函数的原型为:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
此函数返回创建进程的PID,函数中的flags标志用于设置创建子进程时的相关选项,具体含义如下表:
标志 |
含义 |
CLONE_PARENT |
创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子” |
CLONE_FS |
子进程与父进程共享相同的文件系统,包括root、当前目录、umask |
CLONE_FILES |
子进程与父进程共享相同的文件描述符(file descriptor)表 |
CLONE_NEWNS |
在新的namespace启动子进程,namespace描述了进程的文件hierarchy |
CLONE_SIGHAND |
子进程与父进程共享相同的信号处理(signal handler)表 |
CLONE_PTRACE |
若父进程被trace,子进程也被trace |
CLONE_VFORK |
父进程被挂起,直至子进程释放虚拟内存资源 |
CLONE_VM |
子进程与父进程运行于相同的内存空间 |
CLONE_PID |
子进程在创建时PID与父进程一致 |
CLONE_THREAD |
Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群 |
来看下面的例子:
int variable, fd; int do_something() { variable = 42; close(fd); _exit(0); } int main(int argc, char *argv[]) { void **child_stack; char tempch; variable = 9; fd = open("test.file", O_RDONLY); child_stack = (void **) malloc(16384); printf("The variable was %d\n", variable); clone(do_something, child_stack, CLONE_VM|CLONE_FILES, NULL); sleep(1); /* 延时以便子进程完成关闭文件操作、修改变量 */ printf("The variable is now %d\n", variable); if (read(fd, &tempch, 1) < 1) { perror("File Read Error"); exit(1); } printf("We could read from the file\n"); return 0; }
运行输出:
The variable is now 42
File Read Error
程序的输出结果告诉我们,子进程将文件关闭并将变量修改(调用clone时用到的CLONE_VM、CLONE_FILES标志将使得变量和文件描述符表被共享),父进程随即就感觉到了,这就是clone的特点。
sleep
函数调用sleep可以用来使进程挂起指定的秒数,该函数的原型为:
unsigned int sleep(unsigned int seconds);
该函数调用使得进程挂起一个指定的时间,如果指定挂起的时间到了,该调用返回0;如果该函数调用被信号所打断,则返回剩余挂起的时间数(指定的时间减去已经挂起的时间)。
exit
系统调用exit的功能是终止本进程,其函数原型为:
void _exit(int status);
_exit会立即终止发出调用的进程,所有属于该进程的文件描述符都关闭。参数status作为退出的状态值返回父进程,在父进程中通过系统调用wait可获得此值。
wait
wait系统调用包括:
pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options);
wait的作用为发出调用的进程只要有子进程,就睡眠到它们中的一个终止为止; waitpid等待由参数pid指定的子进程退出。
3.进程间通信
Linux的进程间通信(IPC,InterProcess Communication)通信方法有管道、消息队列、共享内存、信号量、套接口等。
管道分为有名管道和无名管道,无名管道只能用于亲属进程之间的通信,而有名管道则可用于无亲属关系的进程之间。
#define INPUT 0 #define OUTPUT 1 void main() { int file_descriptors[2]; /*定义子进程号 */ pid_t pid; char buf[BUFFER_LEN]; int returned_count; /*创建无名管道*/ pipe(file_descriptors); /*创建子进程*/ if ((pid = fork()) == - 1) { printf("Error in fork\n"); exit(1); } /*执行子进程*/ if (pid == 0) { printf("in the spawned (child) process...\n"); /*子进程向父进程写数据,关闭管道的读端*/ close(file_descriptors[INPUT]); write(file_descriptors[OUTPUT], "test data", strlen("test data")); exit(0); } else { /*执行父进程*/ printf("in the spawning (parent) process...\n"); /*父进程从管道读取子进程写的数据,关闭管道的写端*/ close(file_descriptors[OUTPUT]); returned_count = read(file_descriptors[INPUT], buf, sizeof(buf)); printf("%d bytes of data received from spawned process: %s\n", returned_count, buf); } }
上述程序中,无名管道以
int pipe(int filedis[2]);
方式定义,参数filedis返回两个文件描述符filedes[0]为读而打开,filedes[1]为写而打开,filedes[1]的输出是filedes[0]的输入;
在Linux系统下,有名管道可由两种方式创建(假设创建一个名为“fifoexample”的有名管道):
(1)mkfifo("fifoexample","rw");
(2)mknod fifoexample p
mkfifo是一个函数,mknod是一个系统调用,即我们可以在shell下输出上述命令。
有名管道创建后,我们可以像读写文件一样读写之:
/* 进程一:读有名管道*/ void main() { FILE *in_file; int count = 1; char buf[BUFFER_LEN]; in_file = fopen("pipeexample", "r"); if (in_file == NULL) { printf("Error in fdopen.\n"); exit(1); } while ((count = fread(buf, 1, BUFFER_LEN, in_file)) > 0) printf("received from pipe: %s\n", buf); fclose(in_file); } /* 进程二:写有名管道*/ void main() { FILE *out_file; int count = 1; char buf[BUFFER_LEN]; out_file = fopen("pipeexample", "w"); if (out_file == NULL) { printf("Error opening pipe."); exit(1); } sprintf(buf, "this is test data for the named pipe example\n"); fwrite(buf, 1, BUFFER_LEN, out_file); fclose(out_file); }
消息队列用于运行于同一台机器上的进程间通信,与管道相似;
共享内存通常由一个进程创建,其余进程对这块内存区进行读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带
来额外的开销,但在现实中并不常用,因为它控制存取的是实际的物理内存;常用的方式是通过shmXXX函数族来实现共享内存:
int shmget(key_t key, int size, int flag); /* 获得一个共享存储标识符 */
该函数使得系统分配size大小的内存用作共享内存;
void *shmat(int shmid, void *addr, int flag); /* 将共享内存连接到自身地址空间中*/
shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址。此后,进程可以对此地址进行读写操作访问共享内存。
本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:
(1)测试控制该资源的信号量;
(2)若此信号量的值为正,则允许进行使用该资源,进程将进号量减1;
(3)若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1);
(4)当进程不再使用一个信号量控制的资源时,信号量值加1,如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
下面是一个使用信号量的例子,该程序创建一个特定的IPC结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后清除信号量:
#include <stdio.h> #include <sys/types.h> #include <sys/sem.h> #include <sys/ipc.h> void main() { key_t unique_key; /* 定义一个IPC关键字*/ int id; struct sembuf lock_it; union semun options; int i; unique_key = ftok(".", 'a'); /* 生成关键字,字符'a'是一个随机种子*/ /* 创建一个新的信号量集合*/ id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666); printf("semaphore id=%d\n", id); options.val = 1; /*设置变量值*/ semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/ /*打印出信号量的值*/ i = semctl(id, 0, GETVAL, 0); printf("value of semaphore at index 0 is %d\n", i); /*下面重新设置信号量*/ lock_it.sem_num = 0; /*设置哪个信号量*/ lock_it.sem_op = - 1; /*定义操作*/ lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/ if (semop(id, &lock_it, 1) == - 1) { printf("can not lock semaphore.\n"); exit(1); } i = semctl(id, 0, GETVAL, 0); printf("value of semaphore at index 0 is %d\n", i); /*清除信号量*/ semctl(id, 0, IPC_RMID, 0); }
套接字通信并不为Linux所专有,在所有提供了TCP/IP协议栈的操作系统中几乎都提供了socket,而所有这样操作系统,对套接字的编程方法几乎是完全一样的。
4.小节
本章讲述了Linux进程的概念,并以多个实例讲解了进程控制及进程间通信方法,理解这一章的内容可以说是理解Linux这个操作系统的关键。
发表评论
-
多机器执行ssh脚本
2012-08-30 18:12 2190#!/bin/bash Usage() { ... -
Berkeley DB 使用经验总结
2012-08-27 14:41 3032作者:陈磊 NoSQL是现在互联网Web2.0时代备受 ... -
嵌入式数据库系统Berkeley DB
2012-08-27 14:37 1480前言 UNIX/LINUX平台下的数据库种类非常多 ... -
shell中双引号的误用
2011-05-12 15:02 1134for i in "$LIST" ... -
shell for循环
2011-04-06 15:36 914for i in "1 2 3";do e ... -
shell技巧 除法
2011-03-07 11:34 19131shell计算中使用除法,基本默认上都是整除。 比如: ... -
.bash_profile 文件修改之后不执行
2011-02-16 11:21 1879重新登录 不可能不好用的 除非你是从一个用户su oralc ... -
SecureCRT中文显示乱码的解决方法
2011-02-12 11:38 1299最近开始用SecureCRT登陆linux系统,由于是新手,很 ... -
让putty显示中文
2011-02-11 14:43 1381对于经常在windows下远程ssh到linux的用户而言,p ... -
Linux文件类型全解析
2011-01-04 15:13 1366Linux文件类型全解析 ... -
Linux进程状态解析之T、Z、X
2011-01-04 15:09 1760摘要: Linux系统中进程有很多种 ... -
Linux进程状态解析之R、S、D
2011-01-04 14:54 4839摘要: Linu ... -
shell获取参数
2010-12-31 15:29 4136#!/bin/bash Usage() { ... -
Shell实现cgi接口示例
2010-12-30 14:59 1954ls -l /var/www/cgi-bin/ -rwxrw ... -
shell数组
2010-12-29 16:48 1257shell数组 Bash中还可以使用数 ... -
shell 自动化处理
2010-12-29 15:52 998#!/bin/bash sudo yum instal ... -
Linux用户添加sudoer
2010-12-29 11:37 24896使用sudo可以在以非root用户登录时临时获得root权限, ... -
lsof:一个功能强大的命令
2010-12-24 16:30 1325lsof:一个功能强大的命令 lsof命令的原始功能 ... -
linux 的库操作命令 ar和nm
2010-12-22 20:31 1367当我们的程序中有经 ... -
LINUX查看硬件配置 命令
2010-12-21 18:55 1023常用命令整理如下: 查看主板的序列号: dmidec ...
相关推荐
《LinuxC编程实战》系统地介绍了在Linux平台下用C语言进行程序开发的过程,集趣味性、实战性 于一体的160多段代码实例,帮助读者快速掌握在Linux平台下进行C语言程序开发的方法和技巧,并通 过一个原创的BT软件...
目 录 第一篇 Linux和C编程基础 第1章 Linux系统概述 ...第三篇 Linux网络和图形界面编程 第11章 网络编程 第12章 GTK+图形界面编程 第四篇 Linux项目实践 第13章 项目实践:BT下载软件的开发
Linux下的C编程实战.pdf Linux下的C编程实战.pdf Linux下的C编程实战.pdf Linux下的C编程实战.pdf Linux下的C编程实战.pdf Linux下的C编程实战.pdf Linux下的C编程实战.pdf Linux下的C编程实战.pdf Linux下的C编程...
linux+C编程实战linux+C编程实战linux+C编程实战linux+C编程实战
《Linux C编程实战》系统地介绍了在Linux平台下用C语言进行程序开发的过程,集趣味性、实战性于一体的160多段代码实例,帮助读者快速掌握在Linux平台下进行C语言程序开发的方法和技巧,并通过一个原创的BT软件下载...
linuxC编程实战关于linux下C语言的编程,好书啊
Linux C编程实战,Linux C学习,董永清 著,人民邮电出版社
《Linux C语言编程实战》 里所有的源代码。
Linux.C编程实战.pdf
Linux+C编程实战源码 Linux+C编程实战源码Linux+C编程实战源码Linux+C编程实战源码Linux+C编程实战源码Linux+C编程实战源码
本资源为Linux+C编程实战书的附带光盘,含书中的全部源码
Linux C编程实战 源代码 学习Linux平台C编程的优秀的实例代码。
Linux下的C编程实战之开发平台搭建[归纳].pdf
linux c编程实战,完整版。介绍详细,适合学linux c的新手
Linux C 编程实战 高清电子版,需要的小伙伴可以下载哦
董永清所著书籍Linux c编程实战附带光盘资料,内附书中的部分代码。