| Libin's profile绿色家园PhotosBlogLists | Help |
|
November 19 pldatabase -- Objective-C SQL Database Access LibraryDescriptionA SQL database access library for Objective-C, initially focused on SQLite as an application database. The library supports both Mac OS X and iPhone development. Plausible Database is provided free of charge under the BSD license, and may be freely integrated with any application. Download1.2.1 Stable Release1.1.1 Previous ReleaseDocumentation1.2.1 Stable Release1.1.1 Previous ReleaseBuildingTo build your own release binary, build the 'Disk Image' target: user@max:~/pldatabase-1.2> xcodebuild -configuration Release -target 'Disk Image' This will output a new release disk image containing an embeddable Mac OS X framework and a static iPhone framework in build/Release/Plausible Database-{version}.dmg SQLite Resources for iPhone DevelopersSQLite is a public domain database library that offers file and memory based relational database functionality from a single C library. The iPhone / iPod Touch has the SQLite library included and ready to roll and Apple expects the SQLite library to form the backbone of most applications’ data storage requirements. We’ve collected together some of the best SQLite related tutorials, and libraries in order to help you on your way with this essential iPhone programming technology: Tutorials
Reading data from a SQLite Database by Dean Collins is a wonderful “start to finish” tutorial that covers creating an SQLite database, creating an XCode project, and then all the code necessary to use that database within an iPhone app. There’s even a link to an archive of the code used in the tutorial so you can more quickly try it out for yourself. SQLite Tutorial 1: Selecting Data by Jai Kirdatt is the first in five SQLite focused tutorials from the aforementioned author. This one covers creating an SQLite database and, like Dean’s tutorial above, using that database from Objective C. There’s more code and less screenshots than in the aforementioned tutorials, so this tutorial is worth coming to once you’re totally confident with the basics. Source code is available to download at the bottom of the post. SQLite Tutorial 2: Deleting Data by Jai Kirdatt follows on nicely from the previous tutorial and quickly demonstrates how to delete rows of data from an SQLite database.
SQLite Tutorial 4: Loading Data As Required by Jai Kirdatt shows how to select rows in a database to then display in a detail view. Data is only loaded when it is required. SQLite Tutorial 5: Updating Data by Jai Kirdatt shows you how to update the database when fields on a form are edited and the application is then terminated. LibrariesEntropyDB is an embedded object database for OS X 10.5 and iPhone OS written in Objective C that’s built on top of SQLite. It provides a nicer API than that offered by SQLite directly. You don’t need to use SQL at all, but instead just work with Objective C objects! SQLitePersistentObjects allows you to call “save” on your objects and trust that they will be saved properly and can be easily reloaded in future. No SQL required. FMDB is a library that acts as a wrapper around SQLite - inspired by JDBC. fmdb-migration-manager(originally by last week’s podcast interviewee - Dr Nic Williams) allows you to easily roll out database migrations using fmdb. iPhone SQLite Persistence for Objects is an open source project that lets you add “object persistence” to your applications. The object persistence uses SQLite, and the library works on both the iPhone and iPod Touch. Suggestions?Know of any others? Leave a comment and we’ll do our best to include them! SQLite + UITableView for iPhoneI see many people asking for SQLite tutorials around, and since I am using SQLite for the next part in the Advanced RSS Reader Tutorial, I thought I would write up a quick tutorial on using SQLite with the iPhone SDK. 1. Project Requirements I suggest that you have at least a basic understanding of SQLite,writing SQL statements, the XCode interface and using the terminal inOSX. If you don’t know anything about any of these topics then thistutorial probably isn’t for you. 2. Creating our SQLite database for our tutorial We first need to create a database for use with our application. Forthe purposes of this tutorial we will be building a database of animalsalong with a little information on them and a picture. Fire up a new Terminal window and make a new folder to store the database in, here are the commands I ran [pre]cd /Users/lookaflyingdonkey/Documents mkdir SQLiteTutorial cd SQLiteTutorial sqlite3 AnimalDatabase.sql[/pre]You should now be at a “sqlite” command prompt, this is where wewill be building our database structure and entering some test data. For our example we need the name of the animal, a short descriptionand a link to an image. Follow the commands below to create the tableand to enter some sample data. [pre]CREATE TABLE animals ( id INTEGER PRIMARY KEY, name VARCHAR(50), description TEXT, image VARCHAR(255) ); INSERT INTO animals (name, description, image) VALUES ('Elephant', 'The elephant is a very large animal that lives in Africa and Asia', 'http://dblog.com.au/wp-content/elephant.jpg'); INSERT INTO animals (name, description, image) VALUES ('Monkey', 'Monkies can be VERY naughty and often steal clothing from unsuspecting tourists', 'http://dblog.com.au/wp-content/monkey.jpg'); INSERT INTO animals (name, description, image) VALUES ('Galah', 'Galahs are a wonderful bird and they make a great pet (I should know, I have one)', 'http://dblog.com.au/wp-content/galah.jpg'); INSERT INTO animals (name, description, image) VALUES ('Kangaroo', 'Well I had to add the Kangaroo as they are the essence of the Australian image', 'http://dblog.com.au/wp-content/kangaroo.jpg');[/pre]The first command will create the table with the required structureand the next four will insert some test data for us to work with. Toensure that you have entered the data correctly you can execute “SELECT* FROM animals;” and see if it returns the items above. Once you areconfident that everything had been created successfully you can leavethe sqlite command line by typing “.quit”. 3. Creating our Project Now that our database is all ready to go we need to setup our X-Code project. Start off by creating a new “Navigation-Based Application”. ![]() Give your Project a name, I called mine “SQLiteTutorial”. Now set your screen layout to how you prefer it, I suggest makingthe window as large as possible, and making the code view as tall aspossible by dragging the horizontal slider to the top. This will allowyou the most room to move when building your application. Now its time to create the required classes and views for our application, we will start off by making our views. Right Click on the “Resources” folder in the left hand pane andclick “Add File”, we want to create a new “View XIB” under the “UserInterfaces” group. ![]() We now need to give it a name, to stick the Apple’s naming conventions we are going to call it “AnimalViewController.xib”, Now Click “Finish”. Now we need to create two classes, the first one will represent ananimal, right click on the “Classes” folder in the left hand pane,click “Add > New File…”, choose the “NSObject subclass” templateunder the “Cocoa Touch Classes” group and name it “Animal”. The second class will be for our AnimalsViewController, right clickon the “Classes” folder in the left hand pane, click “Add > NewFile…”, choose the “UIViewController subclass” under the “Cocoa TouchClasses” group and name it “AnimalViewController”. 4. Adding SQLite Framework and our Animal Database Now that we have created all of our views and classes it is time to start the real grunt work. First off we need to include the SQLite libraries so our applicationcan utilise them. To do this you will need to right click on the“Frameworks” folder in the left hand pane, then click on “Add >Existing Frameworks…”, then navigate to“/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.0.sdk/usr/lib/”and double click the “libsqlite3.0.dylib” file. A popup will appear,just click “Add” and the library will be added to your project. We also need to add our database we created earlier to the Resourcesfolder, to do this simply right click on the “Resources” folder, click“Add > Existing Files…”, navigate to the location you created thedatabase in then double click on the AnimalDatabase.sql file. Anotherpopup will appear, just click add. All done with the importing, time to code! 5. The Coding begins! We are going to start the coding by building our “Animal” object,every animal will have 3 properties, a name, a description and an imageURL. Open up the “Animal.h” file from the “Classes” folder and edit its contents to look like below, [pre]#import <UIKit/UIKit.h> @interface Animal : NSObject { NSString *name; NSString *description; NSString *imageURL; } @property (nonatomic, retain) NSString *name; @property (nonatomic, retain) NSString *description; @property (nonatomic, retain) NSString *imageURL; -(id)initWithName:(NSString *)n description:(NSString *)d url:(NSString *)u; @end [/pre]Most of the above code should be pretty familiar to you, the only thing that may not be is the initWithName line, this line will allow us to create a new object with the required data, we could have used the default init function, but it will be easier for us to define our own. Now we will actually have to implement the Animal Object, open up the “Animal.m” file and edit its contents to look like below: [pre]#import "Animal.h" @implementation Animal @synthesize name, description, imageURL; -(id)initWithName:(NSString *)n description:(NSString *)d url:(NSString *)u { self.name = n; self.description = d; self.imageURL = u; return self; } @end [/pre]The above code should be pretty easy to read as well, it basicallystores the supplied data from the initWithName function and return theobject (self). Now its time to setup the Application delegate to access the database. Open up the “SQLiteTutorialAppDelegate.h” and edit its contents to look like below: [pre]#import <UIKit/UIKit.h> #import <sqlite3.h> // Import the SQLite database framework @interface SQLiteTutorialAppDelegate : NSObject { UIWindow *window; UINavigationController *navigationController; // Database variables NSString *databaseName; NSString *databasePath; // Array to store the animal objects NSMutableArray *animals; } @property (nonatomic, retain) IBOutlet UIWindow *window; @property (nonatomic, retain) IBOutlet UINavigationController *navigationController; @property (nonatomic, retain) NSMutableArray *animals; @end [/pre]What we are doing here is importing the SQLite database frameworkand creating some variables for storing the database details and anarray of animal objects. Now open up the “SQLiteTutorialAppDelegate.m” file and edit its contents to look like below: [pre]#import "SQLiteTutorialAppDelegate.h" #import "RootViewController.h" #import "Animal.h" // Import the animal object header @implementation SQLiteTutorialAppDelegate @synthesize window; @synthesize navigationController; @synthesize animals; // Synthesize the aminals array - (void)applicationDidFinishLaunching:(UIApplication *)application { // Setup some globals databaseName = @"AnimalDatabase.sql"; // Get the path to the documents directory and append the databaseName NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDir = [documentPaths objectAtIndex:0]; databasePath = [documentsDir stringByAppendingPathComponent:databaseName]; // Execute the "checkAndCreateDatabase" function [self checkAndCreateDatabase]; // Query the database for all animal records and construct the "animals" array [self readAnimalsFromDatabase]; // Configure and show the window [window addSubview:[navigationController view]]; [window makeKeyAndVisible]; } - (void)applicationWillTerminate:(UIApplication *)application { // Save data if appropriate } - (void)dealloc { [animals release]; [navigationController release]; [window release]; [super dealloc]; } -(void) checkAndCreateDatabase{ // Check if the SQL database has already been saved to the users phone, if not then copy it over BOOL success; // Create a FileManager object, we will use this to check the status // of the database and to copy it over if required NSFileManager *fileManager = [NSFileManager defaultManager]; // Check if the database has already been created in the users filesystem success = [fileManager fileExistsAtPath:databasePath]; // If the database already exists then return without doing anything if(success) return; // If not then proceed to copy the database from the application to the users filesystem // Get the path to the database in the application package NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:databaseName]; // Copy the database from the package to the users filesystem [fileManager copyItemAtPath:databasePathFromApp toPath:databasePath error:nil]; [fileManager release]; } -(void) readAnimalsFromDatabase { // Setup the database object sqlite3 *database; // Init the animals Array animals = [[NSMutableArray alloc] init]; // Open the database from the users filessytem if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) { // Setup the SQL Statement and compile it for faster access const char *sqlStatement = "select * from animals"; sqlite3_stmt *compiledStatement; if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) { // Loop through the results and add them to the feeds array while(sqlite3_step(compiledStatement) == SQLITE_ROW) { // Read the data from the result row NSString *aName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)]; NSString *aDescription = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 2)]; NSString *aImageUrl = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 3)]; // Create a new animal object with the data from the database Animal *animal = [[Animal alloc] initWithName:aName description:aDescription url:aImageUrl]; // Add the animal object to the animals Array [animals addObject:animal]; [animal release]; } } // Release the compiled statement from memory sqlite3_finalize(compiledStatement); } sqlite3_close(database); } @end [/pre]Now I know that may look like a fair bit of code and it probablyalso looks quite scary! But really it is quite simple and I have triedto comment nearly every line to describe to you what the line does andwhy it is there. The checkAndCreateDatabase function checks to seeif we have already copied our database from the application bundle tothe users filesystem (in their documents folder), if the databasehasn’t already been created or it has been removed for some reason itwill be recreated from the default database. Next the readAnimalsFromDatabase function will makea connection to the database that is stored in the users documentsfolder, and then executes the SQL statement “SELECT * FROM animals”. Itwill then go through each row that is returned and it will extract thename, description and imageURL from the result and build an Animal object for each. You will see the “sqlite3_column_text” function used here, there are many more of these for returning other field types such as “sqlite3_column_int” for integers, “sqlite3_column_blob” for blobs or “sqlite3_column_value” to get an unknown value. Now that we have the data in our array and we have it in our known format we are ready to start displaying it. Open up the “RootViewController.m” file and edit the numberOfRowsInSection to look like the following: [pre]SQLiteTutorialAppDelegate *appDelegate = (SQLiteTutorialAppDelegate *)[[UIApplication sharedApplication] delegate]; return appDelegate.animals.count; [/pre]What this does is it creates a link to the application delegate, andthen the second line returns the size f the animals array in outApplication delegate, this array was filled previously from the SQLitedatabase. Now in the cellForRowAtIndexPath function you will need at change it to look like the following: [pre]- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease]; } // Set up the cell SQLiteTutorialAppDelegate *appDelegate = (SQLiteTutorialAppDelegate *)[[UIApplication sharedApplication] delegate]; Animal *animal = (Animal *)[appDelegate.animals objectAtIndex:indexPath.row]; [cell setText:animal.name]; return cell; } [/pre]We pretty much just added 3 lines under the “// Set up the cell” line, the first one is the same as we added previously to access the application delegate. The second line creates a new Animalobject based on the array from the application delegate, it will beused to create a row for each individual record in the database. On thefinal line we are just setting the text of the cell to the name fieldfrom the Animal object. You can now run the program and you should see a table view with the4 animals we added to the database, if you added more than my defaultanimals you should see them in here as well. We will now setup the AnimalViewController, open up the “AnimalViewController.h” file and edit its contents to below: [pre]#import <UIKit/UIKit.h> @interface AnimalViewController : UIViewController { IBOutlet UITextView *animalDesciption; IBOutlet UIImageView *animalImage; } @property (nonatomic, retain) IBOutlet UITextView *animalDesciption; @property (nonatomic, retain) IBOutlet UIImageView *animalImage; @end [/pre]What we are doing above is adding an outlet for the description and image for the Animal, we will use these later on when we link the view up. Now open up the “AnimalViewController.m” file and add a synthesize call for for the description and image, this will go under the “@implementation AnimalViewController” line, like so: [pre]#import "AnimalViewController.h" @implementation AnimalViewController @synthesize animalDesciption, animalImage; [/pre]Now it is time to make the detailed view page appear when you selecta record. Open up the “AnimalViewController.xib” file from theresources folder and the interface builder should appear. The first thing we need to do is to set the File’s Owner Class to AnimalViewController, this is done by selecting the “File’s Owner” item in the main window and then clicking Tools > Identity Inspector in the top menu, and then selecting AnimalViewController from the class dropdown. Your inspector window should now look like this: ![]() We are going to be using a UITextView for the description (as it will allow for word wrapping and scrolling in the case that the description is quite large) and a UIImageView to display the image. I have laid mine out like below: ![]() Now that we have everything laid out it is time to link them all up, start by holding control and click+drag from the “File’s Owner” to the “View” objects, a little gray menu will appear and you will need to select view. Now hold control and click+drag from the “File’s Owner” to the UITextView in the layout window, you should see “animalDescription” in the popup list, select it. Repeat this process for the UIImageView and you should see animalImage appear, select it also. Now save the interface and close the interface builder. Nearly done! All we have to do now is to setup the code for when a user presses on a record in the table view. Open up the “RootViewController.h” file and edit its contents to below: [pre]#import <UIKit/UIKit.h> #import "AnimalViewController.h" @interface RootViewController : UITableViewController { AnimalViewController *animalView; } @property(nonatomic, retain) AnimalViewController *animalView; @end [/pre]We are creating an instance of the AnimalViewController to be used bu the RootViewController when a user presses on an item. Now open up the “RootViewController.m” file and edit the top part of the file to look like below: [pre]#import "RootViewController.h" #import "SQLiteTutorialAppDelegate.h" #import "Animal.h" @implementation RootViewController @synthesize animalView; [/pre]This will just synthesize the animalView that we just added. First up lets set the default title of our view, to do this you needto uncomment the viewDidLoad function, and edit it to below: [pre]- (void)viewDidLoad { [super viewDidLoad]; // Uncomment the following line to add the Edit button to the navigation bar. // self.navigationItem.rightBarButtonItem = self.editButtonItem; self.title = @"My Zoo"; } [/pre]We also need to edit the didSelectRowAtIndexPath function in this file, edit it to look like below: [pre]- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // Navigation logic -- create and push a new view controller SQLiteTutorialAppDelegate *appDelegate = (SQLiteTutorialAppDelegate *)[[UIApplication sharedApplication] delegate]; Animal *animal = (Animal *)[appDelegate.animals objectAtIndex:indexPath.row]; if(self.animalView == nil) { AnimalViewController *viewController = [[AnimalViewController alloc] initWithNibName:@"AnimalViewController" bundle:nil]; self.animalView = viewController; [viewController release]; } // Setup the animation [self.navigationController pushViewController:self.animalView animated:YES]; // Set the title of the view to the animal's name self.animalView.title = [animal name]; // Set the description field to the animals description [self.animalView.animalDesciption setText:[animal description]]; // Load the animals image into a NSData boject and then assign it to the UIImageView NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[animal imageURL]]]; UIImage *animalImage = [[UIImage alloc] initWithData:imageData cache:YES]; self.animalView.animalImage.image = animalImage; } [/pre]What we are doing here is checking to see if the animalView object has already been created, if not then create it. The next few lines are used to setup the animation (slide from rightto left) and to set the actual data fields to those of the selectedanimal. Now you should be ready to fire up the application and see it in all its glory. You should see your windows looking like below.
November 17 Useful open source libraries for IPhone DevelopmentIt’s been almost 3 months since the IPhone SDK’s infamous NDA restrictions were lifted, and there are now a good set of open source libraries available for the IPhone. In this post I will go through the ones that I know of one way or another. If you are aware of more high quality open source projects, please let me know in the comments so that I can update this post.
JSON LibraryCurrently, there are 3 choices that I know of: JSON Framework: I used this one in one of my projects, and did not run into problems. Recently after the update to the new SDK 2.2, you may need to make a quick change to your project as outlined at the end of this discussion. Touch JSON: This one is from the same project owners as the TouchXML. Blake Seely’s BSJSONAdditions: This one is little older and intended for the Mac originally. According to the author of the JSON Framework a little slower as per his performance benchmarks DOM XML LibraryIPhone SDK includes an event driven (SAX based) XML parser called NSXMLParser. But if you need DOM style parser, you are out of luck unless you use the libxml2 library directly. There is no MacOS X equivalent of NSXML on the IPhone. However luckily there are at least 2 options that I know of that you might use if you really need the DOM tree: TouchXML: Open source lightweight replacement for Cocoa’s NSXML cluster of classes based onlibxml2. Matt Gallagher: Wrote a lightweight wrapper on top of libxml2 and describes this in his blog entry. However note that in a memory and CPU constrained environment like IPhone, it does make more sense to use an event driven parser (probably why Apple decided not to include NSXML in the first place). There is a recent SDK sample called XMLPerformance that shows parsing large number of items in an efficient way using both the NSXMLParser and the lower level SAX parser provided in libxml2. Google Data APIsSo you would like to access Picasa, Google Calendar, Google Contacts and more… Well there is a very comprehensive ObjectiveC Client Library that you can include as a static library in your applications. Craig Hockenberry of the awesome Twitterific fame, made the source code of the Mobile Twitterific available to serve as a “Best Practices” document. There you can find how to connect to Twitter, get and post tweets etc. FlickrUnfortunately the well-known Cocoa API for Flickr is still not ported to the IPhone. However it looks like they are working on porting it to IPhone as of November 2008. Game EnginesThere are at least 3 game engines that I know of: 2D Gaming – Cocos 2D: This one is intended for 2D games, demos and other graphical/interactive applications. It is a port from the Python based cocos2d design. It uses Open ES 1.1, instead of the Quartz 2D and integrates the Chipmunk 2d physics engine. 3D Gaming – SIO2 Interactive: A 3D engine written in C using OpenGL ES. 3D Gaming – Oolong Engine: Mainly written in C++ with some ObjectiveC. Based on OpenGL ES 1.1 and includes the Bullet 3D physics engine. You can also take a look at some IPhone game source code: Tris: The source code for the Tetris clone that was pulled out of the Appstore earlier. Molecules: This is not a game, but serves as a good tutorial for OpenGL ES development. Unit Testing and Mock FrameworksAs of IPhone SDK 2.2, Apple now includes the OCUnit. This was discussed in the Stanford IPhone Application Programming course and you can download the presentation and the sample. So right of the bat you are now set up for unit testing, no more excuses. Google actually has a more comprehensive framework in their Google Toolbox for Mac that even includes support for UI testing. There is also a mock object framework called OCMock that is designed for MacOS X Cocoa, but witha little bit of work you can get that to work for your IPhone projects. September 23 Chromium 快捷键列表窗口和标签页快捷键
地址栏快捷键在地址栏,进行下列操作之一:
打开谷歌浏览器各功能的快捷键
网页快捷键
文字快捷键
查看更多快捷键
July 17 将linux-2.6.4内核移植到S3C2410摘自:http://www.cublog.cn/u2/70445/showart_1721091.html
1. 构建起环境 要使用内核,首先要编译内核。如果不选择合适的内核和编译器,就会出现错误。典型的就是如下的错误: EG: CC arch/arm/kernel/asm-offsets.s cc1: error : invalid option 'apcs' cc1: error : invalid option 'no-sched-prolog' cc1: error : invalid option 'little-endian' cc1: error : invalid option 'abi=apcs-gnu' arch/arm/kernel/asm-offsets.c:1: error: bad value (armv4) for -march=switch arch/arm/kernel/asm-offsets.c:1: error: bad value (armv4) for -mtune=switch make[1]: *** [arch/arm/kernel/asm-offsets.s] Error 1 make: *** [arch/arm/kernel/asm-offsets.s] Error 2 re:asm-offsets.s 这个文件编译出错一般是编译器问题,不要用2.95.3,用3.4.1 这里主要是编译器版本的问题,2.6内核不能使用以往的2.95.3版本的gcc,需改用3.4.1版本。关于编译器,最好不要自己做,下载网上现成的就可以了。http://www.gnuarm.com/这里有arm的编译器下载。 内核的版本对于编译倒关系不大,一般都能编译成功,而关键还在于配置。为了对s3c2410有更好的支持,建议使用2.6.10以后的版本。网上2.6.11版本用的人也挺多的,相关资料也挺多的,但是新版本对硬件的支持更好更稳定。比如在2.6.11中没有DM9000和CS8900的网卡驱动,在2.6.14版本中就已经对其支持。我选用的是2.6.14版本,现使用三星默认的配置文件arch/arm/configs/s3c2410_defconfig,顺利编译通过。 kernel: http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.11.7.tar.bz2 compiler: ftp://ftp.handhelds.org/projects/toolchain/arm-linux-gcc-3.4.1.tar.bz2
2. 运行内核,显示调试信息 运行内核肯定没问题,但是搞不好你就出现了一下问题: Uncompressing Linux................................................................ done, booting the kernel. 就不动了,以前用2.4.18内核是可以启动的。 re:命令行的console参数错了,应该为console=ttySAC0,不是console=ttyS0。 因为2.6对2410的串口支持已经很好了,使用默认配置的话就不要去怀疑串口驱动了。问题出在命令行上,有人说我将默认的命令行改成了console=ttySAC0也还是不行。这只能说明改动了默认的命令行,只有在bootloader没有传递命令行参数给内核的时候才起作用。如果你的bootloader启动过2.4的内核,命令行参数肯定是不对的,在bootloader中将命令行改了就行了。例如:“console=ttySAC0,115200 root=/dev/ram init=/linuxrc rw initrd=0x30008000,0x320000 ”。 因为烧写bootlader麻烦,时间长,调试阶段不建议修改bootloader。我将内核中的arch/arm/kernel/setup.c文件中的parse_tag_cmdline()函数中的内容注释掉,并且配置正确的CONFIG_CMDLINE参数,即可运行。以后每当改变内核参数只要改变CONFIG_CMDLINE就可以了。(CONFIG_CMDLINE这个值可以在make menuconfig中配置,2.6.11版本和2.6.14版本配置位置有所不同,请注意)。
3. 激动的看到shell 当做完以上工作时看到内核信息哗哗哗的出来已经很兴奋了吧,我也是,但是很快你会看到下面有如下错误: Please append a correct "root=" boot option Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(1,0) re:传递一个合理的root参数,如果已经传递了,请确认内核是否支持 如果使用“console=ttySAC0,115200 root=/dev/ram init=/linuxrc rw initrd=0x30008000,0x320000”这个命令行参数,需要使用ramdisk和initrd。所以确保CONFIG_RAMDISK=y和CONFIG_INITRD=y。在“device driver”=》“Block device”下面可以配置。 这里的ramdisk是把内存块当做一个块设备来使用,相当于一个位于内存中的“磁盘分区”;而initrd就是用于初始化系统的ramdisk,用来mount真正的root。 这里使用initrd是为了调试方便,不需要使用flash和网络。一般使用是多数会用到mtd,调试时使用nfsroot,在移植完相关硬件后再来讨论。 当内核支持了以后还需要有initrd。使用网上下载的现成的,如何制作等会介绍。例如:我有一个initrd为ramdisk.image.gz,在bootloader下加载到指定的0x30800000处,然后启动内核即可。一般出现如下信息表示ramdisk加载成功了。 RAMDISK: Compressed image found at block 0 VFS: Mounted root (ext2 filesystem) readonly. Freeing init memory: 96K Warning: unable to open an initial console. re:unable to open an initial console是因为没有支持devfs。 但是又出现了“Warning: unable to open an initial console.”这个错误。难道是console的问题?no。有些initrd是用于又devfs支持的内核,所以在/dev/目录下没有设备文件。两种办法解决这个问题:1.copy设备节点;2.让内核支持devfs。两个方法没有什么好与差,只是在2.6.14版本中devfs的配置选项在make menuconfig的时候会看不到。linux除去了devfs的配置,提供了替代品udev,但是对这个不熟悉,而且听说也不稳定,所以还是打算用devfs。可以在.config文件中直接添加CONFIG_DEVFS_FS,但由于每次make menuconfig之后会重新写.config文件,每次编译要添加一次,很麻烦,所以不建议。我是把2.6.11版本中的/fs/Kconfig复制过来替换掉。这样配置的时候就能看到devfs的选项了。配置时注意还要选中“Automatical mount at boot”选项。 Freeing init memory: 80K Warning: unable to open an initial console. Kernel panic - not syncing: No init found. Try passing init= option to kernel. re:init参数没有传递,或者制作的ramdisk有问题。 XXX:目前这个地方我也没弄好。正疑惑。用别人的initrd可以,自己用busybox做的就不行。而且用别人的也不是很稳定。有可能时busybox在编译完了后没有copy相关库,需要静态编译。
当解决了以上问题之后,多数能兴奋的看到shell了。
4. 制作自己的initrd 这个initrd的制作可以参考网上一些制作floppy disk的文章。 4.1 制作一个ext2的文件系统映象 ~# dd if=/dev/zero of=ramdisk.image bs=1M count=2 ~# echo y | mke2fs -i 2000 -m 0 ramdisk.image ~# mkdir mnt ~# mount -o loop ramdisk.image mnt 这样,ramdisk.image这个ext2文件系统映象就做完了,他相当于一个磁盘分区。
5.显示自定义logo 在配置内核的时候选中了启动Logo的支持。 使用下面的方法可以将企鹅的Logo换成自己喜欢的任意图片。 首先准备一幅自己喜欢的图片,然后将背景涂成黑色。然后将该图片保存成png格式,例如linuxlogo.png。在Linux下使用下面的命令:
# pngtopnm linuxlogo.png > linuxlogo.pnm # pnmquant 224 linuxlogo.pnm > linuxlogo224.pnm # pnmtoplainpnm linuxlogo224.pnm > linuxlogo224.ppm
然后.ppm替换/usr/src/linux-2.6.8.1/drivers/后用生成的linuxlogo224video/logo/logo_linux_clut224.ppm(最好先做好备份),然后删除同一目录下的logo_linux_clut224.c文件,重新编译内核,启动之后就可以在屏幕左上方看到自己的Logo了。 May 30 介绍qmakeqmake是用来为不同的平台的开发项目创建makefile的Trolltech开发一个易于使用的工具。qmake简化了makefile的生成,所以为了创建一个makefile只需要一个只有几行信息的文件。qmake可以供任何一个软件项目使用,而不用管它是不是用Qt写的,尽管它包含了为支持Qt开发所拥有的额外的特征。 qmake基于一个项目文件这样的信息来生成makefile。项目文件可以由开发者生成。项目文件通常很简单,但是如果需要它是非常完善的。不用修改项目文件,qmake也可以为为Microsoft Visual Studio生成项目。 qmake的概念QMAKESPEC环境变量举例来说,如果你在Windows下使用Microsoft Visual Studio,然后你需要把QMAKESPEC环境变量设置为win32-msvc。如果你在Solaris上使用gcc,你需要把QMAKESPEC环境变量设置为solaris-g++。 在qt/mkspecs中的每一个目录里面,都有一个包含了平台和编译器特定信息的qmake.conf文件。这些设置适用于你要使用qmake的任何项目,请不要修改它,除非你是一个专家。例如,假如你所有的应用程序都必须和一个特定的库连接,你可以把这个信息添加到相应的qmake.conf文件中。 项目(.pro)文件一个项目文件是用来告诉qmake关于为这个应用程序创建makefile所需要的细节。例如,一个源文件和头文件的列表、任何应用程序特定配置、例如一个必需要连接的额外库、或者一个额外的包含路径,都应该放到项目文件中。 “#”注释你可以为项目文件添加注释。注释由“#”符号开始,一直到这一行的结束。 模板模板变量告诉qmake为这个应用程序生成哪种makefile。下面是可供使用的选择:
“app”模板“app”模板告诉qmake为建立一个应用程序生成一个makefile。当使用这个模板时,下面这些qmake系统变量是被承认的。你应该在你的.pro文件中使用它们来为你的应用程序指定特定信息。
你只需要使用那些你已经有值的系统变量,例如,如果你不需要任何额外的INCLUDEPATH,那么你就不需要指定它,qmake会为所需的提供默认值。例如,一个实例项目文件也许就像这样: TEMPLATE = app 如果条目是单值的,比如template或者目的目录,我们是用“=”,但如果是多值条目,我们使用“+=”来为这个类型添加现有的条目。使用“=”会用新值替换原有的值,例如,如果我们写了DEFINES=QT_DLL,其它所有的定义都将被删除。 “lib”模板“lib”模板告诉qmake为建立一个库而生成makefile。当使用这个模板时,除了“app”模板中提到系统变量,还有一个VERSION是被支持的。你需要在为库指定特定信息的.pro文件中使用它们。
“subdirs”模板“subdirs”模板告诉qmake生成一个makefile,它可以进入到特定子目录并为这个目录中的项目文件生成makefile并且为它调用make。 在这个模板中只有一个系统变量SUBDIRS可以被识别。这个变量中包含了所要处理的含有项目文件的子目录的列表。这个项目文件的名称是和子目录同名的,这样qmake就可以发现它。例如,如果子目里是“myapp”,那么在这个目录中的项目文件应该被叫做myapp.pro。 CONFIG变量配置变量指定了编译器所要使用的选项和所需要被连接的库。配置变量中可以添加任何东西,但只有下面这些选项可以被qmake识别。 下面这些选项控制着使用哪些编译器标志:
下面这些选项定义了所要连编的库/应用程序的类型:
例如,如果你的应用程序使用Qt库,并且你想把它连编为一个可调试的多线程的应用程序,你的项目文件应该会有下面这行: CONFIG += qt thread debug 注意,你必须使用“+=”,不要使用“=”,否则qmake就不能正确使用连编Qt的设置了,比如没法获得所编译的Qt库的类型了。 May 29 编译meshlab 1.21全接触1. 编译环境: a. visual studio 2008 perfessional edition,因为已经安装了sp1,所以不确定它会对编译有何影响。 b. QT opensource v4.5。0,只编译了其动态库文件(静态库无法编译完全)。 执行“Visual Studio 2008 命令提示 ”控制台工具后,在QT根目录执行QT编译环境设置脚本,该脚本同时为meshlab生成vc 项目工程的环境。一下为该脚本原文: @echo off set cur_dir=%cd%\ set QTDIR=%cur_dir% set QMAKESPEC=win32-msvc2008 set ConfPara=-debug-and-release -opensource -fast -no-dbus -no-webkit
set PATH=%QTDIR%/bin;%PATH% set INCLUDE=%MINGWDIR%/include;%QTDIR%/include;%QWTDIR%/src;%LOG4QTDIR%/src;%INCLUDE% set LIB=%MINGWDIR%/lib;%QTDIR%/lib;%QWTDIR%/lib;%LIB%
echo *********************************************************************** echo Created By gmail:bygreencn.gmail.com echo Includes : QT 4.5.0、Visual Studio 2008 echo QT : %QTDIR% echo QMAKESPEC: %QMAKESPEC% echo ConfPara: %ConfPara% echo *********************************************************************** @REM pause @REM nmake clean @REM nmake confclean @REM configure.exe %ConfPara%
@REM pause @REM echo build it now? @REM nmake clean cmd /k
c.下载MeshLab's source code version 1.2.1,我下载的是All Inclusive package 2. 生成所需的VC项目工程文件 a. 上一步的控制台,进入.\ meshlab\src\external,执行qmake -tp vc -recursive external.pro b. 上一步的控制台,进入.\ meshlab\src,执行qmake -tp vc -recursive meshlabv12.pro 3. 编译meshlab a. 首先编译external library。用vc打开.\ meshlab\src\external\external.sln,进入配置管理器,选择编译debug或release版本,在选择生成解决方案,等待编译全部通过,它会生成三个库文件。bz.lib,3ds.lib和muparser.lib,我发现3ds.lib的生成有些问题,会使得meshlab在LINK时无法正确连接函数,我在lib3ds\type.h做了一下修改: //#ifdef _MSC_VER //#ifdef LIB3DS_EXPORTS //#define LIB3DSAPI __declspec(dllexport) //#else //#define LIB3DSAPI __declspec(dllimport) //#endif //#else #define LIB3DSAPI //#endif b.编译meshlab:meshlab所有的plugin工程的设置有些问题,这些工程都制定输出为动态库,但是输出文件却指定为输出为*****.lib,这导致所有的plugins的工程都失败。我的做法是把指定输出为*****.dll,这里应该必须为dll,因为New的对话框中会根据plugins文件夹下的内容动态生成,因为这些应该也是动态加载的,因为静态库是不行的了。 1). 需要为io_lib指定其需要的lib3ds.lib(external library) 2). 需要为io_epoch指定其需要的bz2.lib和头文件的位置(external library) 如果哪个plugin工程出现类似这个错误: 1>Project : error PRJ0019: 某个工具从以下位置返回了错误代码: "MOC v3dImportDialog.h" 1>项目: warning PRJ0018 : 未找到下列环境变量: 1>$(QTDIR) 只需要用生成VC项目的那个控制台环境进入相应的plugin目录,执行qmake -tp vc -recursive xxxxxx.pro来重新生成该工程即可。
编译整个项目,大概需要十多分钟。然后就可以进入.\meshlab\src\meshlab\debug或者.\meshlab\src\meshlab\release执行meshlab.exe;记得要把QT的DLL库文件拷贝到这个目录或者将QT的DLL库所在目录加入到系统PATH中啊。
May 13 破解无线路由器密码Posted by chinahang 2009年5月13日
随着社会的进步!WIFI上网日益普及,特别是大城市中随便在一个小区搜索一下就能找到好多热点,搜索到热点然后链接上去那么我们就可以尽情的享受免费上网服务了。 破解静态WEP KEY全过程
发现 首先通过NetStumbler确认客户端已在某AP的覆盖区内,并通过AP信号的参数进行‘踩点’(数据搜集)。 通 过上图的红色框框部分内容确定该SSID名为demonalex的AP为802.11b类型设 备,Encryption属性为‘已加密’,根据802.11b所支持的算法标准,该算法确定为WEP。有一点需要注意:NetStumbler对任何有 使用加密算法的STA[802.11无线站点]都会在Encryption属性上标识为WEP算法,如上图中SSID为gzpia的AP使用的加密算法是 WPA2-AES。 破解下载Win32版AirCrack程序集---WinAirCrackPack工具包(下载地址:http://www.demonalex.net/download/wireless/aircrack/WinAircrackPack.zip)。解压缩后得到一个大概4MB的目录,其中包括六个EXE文件: aircrack.exe 原WIN32版aircrack程序 airdecap.exe WEP/WPA解码程序 airodump.exe 数据帧捕捉程序 Updater.exe WIN32版aircrack的升级程序 WinAircrack.exe WIN32版aircrack图形前端 wzcook.exe 本地无线网卡缓存中的WEPKEY记录程序
我们本次实验的目的是通过捕捉适当的数据帧进行IV(初始化向量)暴力破解得到WEP KEY,因此只需要使用airodump.exe(捕捉数据帧用)与WinAircrack.exe(破解WEP KEY用)两个程序就可以了。 首先打开ariodump.exe程序,按照下述操作:
选
择‘Key size’为64(目前大多数用户都是使用这个长度的WEP
KEY,因此这一步骤完全是靠猜测选定该值),最后单击主界面右下方的‘Aircrack the
key…’按钮,此时将弹出一个内嵌在cmd.exe下运行的进程对话框,并在提示得出WEP KEY: 利用打开无线网卡的连接参数设置窗口,设置参数为: SSID:demonalex 频道:6 WEP KEY:1111122222(64位) OK,现在可以享受连入别人WLAN的乐趣了。 April 28 让VS 2008支持Subversion插件AnkhsvnVisual
Studio 2005 有一个开源的Subversion插件,Ankhsvn
(http://ankhsvn.tigris.org/),安装后,VS
2005中将内置Subversion的支持,可以直接在VS里面提交修改。我经常用它和TortoiseSVN
配合来使用Subversion,十分方便。
再次打开Visual Studio 2008后,就会发现Ankhsvn已经集成进系统了。 Dim shell, filename, fso, file, content Set shell = CreateObject("wscript.shell") Set fso = CreateObject("Scripting.FileSystemObject") filename = "ankh.reg"![]() shell.run "reg export HKLMSOFTWAREMicrosoftVisualStudio8.0AddinsAnkh " & filename, 1, True![]() Set file = fso.OpenTextFile(filename, 1, False, True) content = file.ReadAll content = Replace(content, "VisualStudio8.0", "VisualStudio9.0") content = Replace(content, ".NET 2005", ".NET 2008") file.Close()![]() Set file = fso.OpenTextFile(filename, 2, True) file.Write content file.Close()![]() shell.run "reg import " & filename, 1, true![]() fso.DeleteFile filenameApril 16 Gpredict is a real-time satellite tracking and orbit prediction applicationGpredict ScreenshotsBelow you can see some sample screenshots of gpredict in in action. They illustrate the main features of gpredict. I have a whole album dedicated to screenshots of Gpredict in my Media Gallery. You can also post links to your own screenshots and pictures of Gpredict in action on the forum. Main Window, Modules Layouts, and ViewsFuture Pass PredictionsRadio and Antenna Rotator ControlPreferences and SettingsApril 14 用VC6.0编译boost 1.38.01. 从boost.org下载下1.38.0的源码 2.编译jam 在boost_1_38_0\tools\jam\src下有个build.bat ,修改其ProgramFiles变量为VC安装的目录。 执行build.bat msvc,等编译成功,会在\boost_1_38_0\tools\jam\src\bin.ntx86下生成bjam.exe 3.将bjam拷贝到boost_1_38_0\bin,将目录增加环境变量PATH。 4.编译整个boost 在根目录boost_1_38_0,执行bjam --toolset=msvc-6.0
另外: 5. 如果编译boost里面单独的模块,如regex 进入boost_1_38_0\libs\regex\build 执行bjam bjam --toolset=msvc-6.0 就会在boost_1_38_0\bin.v2\libs\regex\build\msvc-6.0\debug\threading-multi下面生成boost_regex-vc6-mt-gd-1_38_0.dll和boost_regex-vc6-mt-gd-1_38_0.lib库,可以给VC6.0使用。 March 23 Git 中文教程介绍Git --- The stupid content tracker, 傻瓜内容跟踪器。Linus 是这样给我们介绍 Git 的。 Git 是用于 Linux 内核开发的版本控制工具。与常用的版本控制工具 CVS, Subversion 等不同, 它采用了分布式版本库的方式,不必服务器端软件支持,使源代码的发布和交流极其方便。 Git 的速度很快,这对于诸如 Linux kernel 这样的大项目来说自然很重要。 Git 最为出色的是它的合并跟踪(merge tracing)能力。 实际上内核开发团队决定开始开发和使用 Git 来作为内核开发的版本控制系统的时候, 世界开源社群的反对声音不少,最大的理由是 Git 太艰涩难懂,从 Git 的内部工作机制来说,的确是这样。 但是随着开发的深入,Git 的正常使用都由一些友好的脚本命令来执行,使 Git 变得非常好用, 即使是用来管理我们自己的开发项目,Git 都是一个友好,有力的工具。 现在,越来越多的著名项目采用 Git 来管理项目开发,例如:wine, U-boot 等,详情看 http://www.kernel.org/git 作为开源自由原教旨主义项目,Git 没有对版本库的浏览和修改做任何的权限限制。 它只适用于 Linux / Unix 平台,没有 Windows 版本,目前也没有这样的开发计划。 本文将以 Git 官方文档 Tutorial, core-tutorial 和 Everyday GIT 作为蓝本翻译整理,但是暂时去掉了对 Git 内部工作机制的阐述, 力求简明扼要,并加入了作者使用 Git 的过程中的一些心得体会,注意事项,以及更多的例子。 建议你最好通过你所使用的 Unix / Linux 发行版的安装包来安装 Git, 你可以在线浏览本文 ,也可以通过下面的命令来得到本文最新的版本库,并且通过后面的学习用 Git 作为工具参加到本文的创作中来。 $ git-clone http://www.bitsun.com/git/gittutorcn.git 创建一个版本库:git-init-db创建一个 Git 版本库是很容易的,只要用命令 git-init-db 就可以了。 现在我们来为本文的写作创建一个版本库: $ mkdir gittutorcn git 将会作出以下的回应 defaulting to local storage area 这样,一个空的版本库就创建好了,并在当前目录中创建一个叫 .git 的子目录。 你可以用 ls -a 查看一下,并请注意其中的三项内容:
具体地说,子目录 refs 包含着两个子目录叫 heads 和 tags, 就像他们的名字所表达的意味一样:他们存放了不同的开发分支的头的索引, 或者是你用来标定版本的标签的索引。 请注意:master 是默认的分支,这也是为什么 .git/HEAD 创建的时候就指向 master 的原因,尽管目前它其实并不存在。 git 将假设你会在 master 上开始并展开你以后的工作,除非你自己创建你自己的分支。 另外,这只是一个约定俗成的习惯而已,实际上你可以将你的工作分支叫任何名字, 而不必在版本库中一定要有一个叫 master 的分支,尽管很多 git 工具都认为 master 分支是存在的。 现在已经创建好了一个 git 版本库,但是它是空的,还不能做任何事情,下一步就是怎么向版本库植入数据了。 植入内容跟踪信息:git-add为了简明起见,我们创建两个文件作为练习: $ echo "Hello world" > hello 我们再用 git-add 命令将这两个文件加入到版本库文件索引当中: $ git-add hello example git-add 实际上是个脚本命令,它是对 git 内核命令 git-update-index 的调用。 因此上面的命令和下面的命令其实是等价的: $ git-update-index --add hello example 如果你要将某个文件从 git 的目录跟踪系统中清除出去,同样可以用 git-update-index 命令。例如: $ git-update-index --force-remove foo.c
应该建立一个清晰的概念就是,git-add 和 git-update-index 只是刷新了 git 的跟踪信息,hello 和 example 这两个文件中的内容并没有提交到 git 的内容跟踪范畴之内。 提交内容到版本库:git-commit既然我们刷新了 Git 的跟踪信息,现在我们看看版本库的状态: $ git-status 我们能看到 git 的状态提示: # 提示信息告诉我们版本库中加入了两个新的文件,并且 git 提示我们提交这些文件, 我们可以通过 git-commit 命令来提交: $ git-commit -m "Initial commit of gittutor reposistory" 查看当前的工作:git-diffgit-diff 命令将比较当前的工作目录和版本库数据库中的差异。 现在我们编辑一些文件来体验一下 git 的跟踪功能。 $ echo "It's a new day for git" >> hello 我们再来比较一下,当前的工作目录和版本库中的数据的差别。 $ git-diff 差异将以典型的 patch 方式表示出来: diff --git a/hello b/hello 此时,我们可以再次使用组合命令 git-update-index 和 git-commit 将我们的工作提交到版本库中。 $ git-update-index hello 实际上,如果要提交的文件都是已经纳入 git 版本库的文件,那么不必为这些文件都应用 git-update-index 命令之后再进行提交,下面的命令更简捷并且和上面的命令是等价的。 $ git-commit -a -m "new day for git" 管理分支:git-branch直至现在为止,我们的项目版本库一直都是只有一个分支 master。 在 git 版本库中创建分支的成本几乎为零,所以,不必吝啬多创建几个分支。 下面列举一些常见的分支策略,仅供大家参考:
创建分支下面的命令将创建我自己的工作分支,名叫 robin,并且将以后的工作转移到这个分支上开展。 $ git-branch robin 删除分支要删除版本库中的某个分支,使用 git-branch -D 命令就可以了,例如: $ git-branch -D branch-name 查看分支运行下面的命令可以得到你当前工作目录的分支列表: $ git-branch 如果你忘记了你现在工作在哪个分支上,运行下面的命令可以告诉你: $ cat .git/HEAD 查看项目的发展变化和比较差异这一节介绍几个查看项目的版本库的发展变化以及比较差异的很有用的命令: git-show-branchgit-diffgit-whatchanged我们现在为 robin, master 两个分支都增加一些内容。 $ git-checkout robin $ git-checkout master git-show-branch 命令可以使我们看到版本库中每个分支的世系发展状态, 并且可以看到每次提交的内容是否已进入每个分支。 $ git-show-branch 这个命令让我们看到版本库的发展记录。 * [master] Some fun 譬如我们要查看世系标号为 master^ 和 robin 的版本的差异情况, 我们可以使用这样的命令: $ git-diff master^ robin 我们可以看到这两个版本的差异: diff --git a/hello b/hello
我们现在再用 git-whatchanged 命令来看看 master 分支是怎么发展的。 $ git-checkout master diff-tree 1d2fa05... (from 3ecebc0...) 从上面的内容中我们可以看到,在 robin 分支中的日志为 "Some work" 的内容, 并没有在 master 分支中出现。 合并两个分支:git-merge既然我们为项目创建了不同的分支, 那么我们就要经常地将自己或者是别人在一个分支上的工作合并到其他的分支上去。 现在我们看看怎么将 robin 分支上的工作合并到 master 分支中。 现在转移我们当前的工作分支到 master,并且将 robin 分支上的工作合并进来。 $ git-checkout master $ git-checkout master 但是,此时 git 会出现合并冲突提示: Trying really trivial in-index merge... git 的提示指出,在合并作用于文件 hello 的 'Some fun' 和 'some work' 这两个对象时有冲突, 具体通俗点说,就是在 master, robin 这两个分支中的 hello 文件的某些相同的行中的内容不一样。 我们需要手动解决这些冲突,现在先让我们看看现在的 hello 文件中的内容。 $ cat hello 此时的 hello 文件应是这样的,用过其他的版本控制系统的朋友应该很容易看出这个典型的冲突表示格式: Hello World 我们用编辑器将 hello 文件改为: Hello World 现在可以将手动解决了冲突的文件提交了。 $ git-commit -i hello 以上是典型的两路合并(2-way merge)算法,绝大多数情况下已经够用。 但是还有更复杂的三路合并和多内容树合并的情况。详情可参看: git-read-tree, git-merge 等文档。 逆转与恢复:git-reset
项目跟踪工具的一个重要任务之一,就是使我们能够随时逆转(Undo)和恢复(Redo)某一阶段的工作。
git-reset 命令就是为这样的任务准备的。 它将当前的工作分支的 头 定位到以前提交的任何版本中,它有三个重置的算法选项。 命令形式:git-reset [--mixed | --soft | --hard] [<commit-ish>] 命令的选项:
一个重要技巧--逆转提交与恢复可能有人会问,--soft 选项既不重置头索引的位置,也不改变工作树中的内容, 那么它有什么用呢?现在我们介绍一个 --soft 选项的使用技巧。 下面我们用例子来说明: $ git-checkout master 这里我们创建了一个 master 的拷贝分支 softreset, 现在我们可以看到两个分支是在同一起跑线上的。 ! [master] Merge branch 'robin' 我们为 文件增加一些内容并提交。 $ echo "Botch, botch, botch" >> hello 我们可以看到此时 softreset 比 master 推进了一个版本 "some botch" 。 ! [master] Merge branch 'robin' 现在让我们来考虑这样的一种情况,假如我们现在对刚刚提交的内容不满意, 那么我们再编辑项目的内容,再提交的话,那么 "some botch" 的内容就会留在版本库中了。 我们当然不希望将有明显问题的内容留在版本库中,这个时候 --soft 选项就很有用了。 为了深入了解 --soft 的机制,我们看看现在 softreset 分支的头和 ORIG_HEAD 保存的索引。 $ cat .git/refs/heads/softreset .git/ORIG_HEAD 结果如下: 5e7cf906233e052bdca8c598cad2cb5478f9540a 现在用 --soft 选项逆转刚才提交的内容: git-reset --soft HEAD^ 现在让我们再看看 .git/ORIG_HEAD 的中保存了什么? $ cat .git/ORIG_HEAD 结果如下: 5e7cf906233e052bdca8c598cad2cb5478f9540a 看!现在的 .git/ORIG_HEAD 等于逆转前的 .git/refs/heads/softreset 。 也就是说,git-reset --soft HEAD^ 命令逆转了刚才提交的版本进度, 但是它将那次提交的对象的索引拷贝到了 .git/ORIG_HEAD 中。 我们再编辑 hello 文件成为下面的内容: Hello World 我们甚至可以比较一下现在的工作树中的内容和被取消了的那次提交的内容有什么差异: $ git-diff ORIG_HEAD 结果如下: diff --git a/hello b/hello 接着,我们可以恢复刚才被取消了的那次提交了。 $ git-commit -a -c ORIG_HEAD 注意,这个命令会打开默认的文本编辑器以编辑原来提交的版本日志信息,我们改为 "nice work" 。 大家可以自行用 git-show-branch 命令来查看一下现在的分支状态。 并且我们还可以不断地重复上述的步骤,一直修改到你对这个版本进度满意为止。 git-reset 命令还有很多的用途和技巧,请参考 git-reset ,以及 Everyday GIT with 20 commands or So 。 提取版本库中的数据这是个很有用的小技巧,如果你对你现在的工作目录下的东西已经不耐烦了, 随时可以取出你提交过的东西覆盖掉当前的文件,譬如: $ git-checkout -f foo.c 标定版本在 git 中,有两种类型的标签,“轻标签”和“署名标签”。 技术上说,一个“轻标签”和一个分支没有任何区别,只不过我们将它放在了 .git/refs/tags/ 目录, 而不是 heads 目录。因此,打一个“轻标签”再简单不过了。 $ git-tag my-first-tag 如果你打算针对某个commit ID来打标签,虽然该命令可以通过gitk里的右键菜单来实现,但是该命令对实际应用是很有帮助的。 $ git-tag mytag f0af6283824688f9d23426031734657661b54388 “署名标签”是一个真正的 git 对象,它不但包含指向你想标记的状态的指针,还有一个标记名和信息, 可选的 PGP 签名。你可以通过 -a 或者是 -s 选项来创建“署名标签”。 $ git-tag -s <tag-name> 合并外部工作通常的情况下,合并其他的人的工作的情况会比合并自己的分支的情况要多, 这在 git 中是非常容易的事情,和你运行 git-merge 命令没有什么区别。 事实上,远程合并的无非就是“抓取(fetch)一个远程的版本库中的工作到一个临时的标签中”, 然后再使用 git-merge 命令。 可以通过下面的命令来抓取远程版本库: $ git-fetch <remote-repository> 根据不同的远程版本库所使用的通讯协议的路径来替代上面的 remoted-repository 就可以了。
到这里可能有些朋友已经想到, 实际上,我们可以通过 Rsync, SSH 之类的双向传输方式来建立类似 CVS,SVN 这样的中心版本库模式的开发组织形式。 通过电子邮件交换工作读过上一节之后,有的朋友可能要问,如果版本库是通过单向的下载协议发布的,如 HTTP, 我们就无法将工作上传到公共的版本库中。别人也不能访问我的机器来抓取我的工作,那怎么办呢? 不必担心,我们还有 email !别忘了 git 本来就是为了管理 Linux 的内核开发而设计的。 所以,它非常适合像 Linux Kernel 这样的开发组织形式高度分散,严重依赖 email 来进行交流的项目。 下面模拟你参加到《Git 中文教程》的编写工作中来,看看我们可以怎么通过 email 进行工作交流。 你可以通过下面的命令下载这个项目的版本库。 $ git-clone http://www.bitsun.com/git/gittutorcn.git
你可以直接在 master 下开展工作,也可以创建你自己的工作分支。 当你对项目做了一定的工作,并提交到库中。我们用 git-show-branch 命令先看下库的状态。 * [master] your buddy's contribution 上面就假设你已经提交了一个叫 "your buddy's contribution" 的工作。 现在我们来看看怎么通过 email 来交流工作了。 $ git-fetch origin (1) 上面的几个命令,会在当前目录下生成一个大概名为 0001-your-buddy-s-contribution.txt 补丁文件, 建议你用文本工具查看一下这个文件的具体形式,然后将这个文件以附件的形式发送到项目维护者的邮箱: vortune@gmail.com 当项目的维护者收到你的邮件后,只需要用 git-am 命令,就可以将你的工作合并到项目中来。 $ git-checkout -b buddy-incomming 用 Git 协同工作假设 Alice 在一部机器上自己的个人目录中创建了一个项目 /home/alice/project, Bob 想在同一部机器自己的个人目录中为这个项目做点什么。 Bob 首先这样开始: $ git-clone /home/alice/project myrepo 这样就创建了一个保存着 Alice 的版本库的镜像的新目录 "myrepo"。 这个镜像保存着原始项目的起点和它的发展历程。 接着 Bob 对项目做了些更改并提交了这些更改: (编辑一些文件) 当他搞定之后,他告诉 Alice 将他的东西从 /home/bob/myrepo 中引入,她只需要这样: $ cd /home/alice/project 这样就将 Bob 的版本库中的 "master" 分支的变化引入了。 Alice 也可以通过在 pull 命令的后面加入参数的方式来引入其他的分支。 在导入了 Bob 的工作之后,用 "git-whatchanged" 命令可以查看有什么信的提交对象。 如果这段时间里以来,Alice 也对项目做过自己的修改,当 Bob 的修改被合并进来的时候, 那么她需要手动修复所有的合并冲突。 谨慎的 Alice 在导入 Bob 的工作之前,希望先检查一下。 那么她可以先将 Bob 的工作导入到一个新创建的临时分支中, 以方便研究 Bob 的工作: $ git fetch /home/bob/myrepo master:bob-incoming 这个命令将 Bob 的 master 分支的导入到名为 bob-incoming 的分支中( 不同于 git-pull 命令,git-fetch 命令只是取得 Bob 的开发工作的拷贝, 而不是合并经来)。接着: $ git whatchanged -p master..bob-incoming 这会列出 Bob 自取得 Alice 的 master 分支之后开始工作的所有变化。 检查过这些工作,并做过必须的调整之后, Alice 就可以将变化导入到她的 master 分支中: $ git-checkout master 最后的命令就是将 "bob-incoming" 分支的东西导入到 Alice 自己的版本库中的, 稍后,Bob 就可以通过下面的命令同步 Alice 的最新变化。 $ git-pull 注意不需为这个命令加入 Alice 的版本库的路径,因为当 Bob 克隆 Alice 的版本库的时候, git 已经将这个路径保存到 .git/remote/origin 文件中,它将会是所以的导入操作的默认路径。 Bob 可能已经注意到他并没有在他的版本库中创建过分支(但是分支已经存在了): $ git branch "origin" 分支,它是运行 "git-clone" 的时候自动创建的,他是 Alice 的 master 分支的原始镜像, Bob 应该永远不要向这个分支提交任何东西。 如果 Bob 以后决定在另外一部主机上开展工作,那么他仍然需要通过 SSH 协议从新克隆和导入( Alice 的版本库): $ git-clone alice.org:/home/alice/project/ myrepo 我们可以使用 git 自然协议,或者是 rsync, http 等协议的任何一种,详情请参考 git-pull。 Git 同样可以建立类似 CVS 那样的开发模式,也就是所有开发者都向中心版本库提交工作的方式, 详情参考 git_push 和 git for CVS users 。 为版本库打包在前面,我们已经看到在 .git/objects/??/ 目录中保存着我们创建的每一个 git 对象。 这样的方式对于自动和安全地创建对象很有效,但是对于网络传输则不方便。 git 对象一旦创建了,就不能被改变,但有一个方法可以优化对象的存储,就是将他们“打包到一起”。 $ git repack 上面的命令让你做到这点,如果你一直是做着我们的例子过来的, 你现在大约会在 .git/objects/??/ 目录下积累了17个对象。 git-repack 会告诉你有几个对象被打包了, 并且将他们保存在 .git/objects/pack 目录当中。
如果你是个偏执狂,就运行一下 git-verity-pack 命令来检查一下有缺陷的包吧, 不过,其实你无须太多担心,我们的程序非常出色 ;-). 一旦你已经对那些对象打包了,那么那些已经被打过包的原始的对象,就没有必要保留了。 $ git prune-packed 会帮你清楚他们。 如果你好奇的话,你可以在执行 git-prune-repacked 命令之前和之后, 都运行一下 find .git/objects -type f,这样你就能看到有多少没有打包的对象, 以及节省了多少磁盘空间。
如果你此时再次运行 git-repack,它就会说 "Nothing to pack"。 要是你继续开发,并且积累了一定数量的变迁,再运行 git-repack 将会创建一个新的包, 它会包含你自上次对库打包以来创建的对象。我们建议你尽快在初始化提交之后打包一下你的版本库( 除非你现在的项目是个涂鸦式的草稿项目),并且在项目经历过一段很活跃的时期时, 再运行 git-repack 一下。 当一个版本库通过 git-push 和 git-pull 命令来同步源版本库中打包过的对像的时候, 通常保存到目标版本库中的是解包了的对象,除非你使用的是 rsync(远程同步协议)协议的传输方式。 正是这种容许你在两头的版本库中有不同的打包策略的方式,他意味着你也许在过一段时间之后, 需要在两头的版本库中都重新打包一下。 发布你的工作我们可以通过一个远程的版本库来利用他人的工作,但是,你如何准备一个自己的版本库来供其他人下载呢? 你在自己的工作目录下进行工作,这样你的版本库就被作为.git的一个子目录放在你的工作树下。 你可以让其他人来远程的访问你的版本库,但是实际上这不是通常的做法。推荐的做法是创建一个公共的版本库, 让它可供其他人访问,并且,当你在你的工作目录下做了很好的改动时,你可以更新到公共的版本库中。这通常称为pushing。
从你的本地的(私有的)版本库中发布改动到你的远程的(公共的)版本库中需要远程机器上的写权限。你需要一个SSH的 帐号来运行一个简单的命令,git-receive-pack。 首先,你需要在远程机器上创建一个空的版本库来存放你的公共版本库。这个空版本库以后将通过pushing来保持更新。 显然,这个版本库之需要在开始的时候创建一次。
你本地的版本库的git目录通常是.git,但是你的公共版本库通常还要加上你的项目名,即.git。 让我们来为my-git创建这样一个版本库。首先,登入远程的机器,创建一个空目录(如果你选择HTTP作为发布方法, 这个空目录需要建在web server的根目录下面): $ mkdir my-git.git 然后运行git init-db命令将这个目录加入git版本库中,这里,因为这个版本库的名字不是通常的.git, 我们需要稍微改动一下命令: $ GIT_DIR=my-git.git git-init-db 有很多种传输方式可以发布公共版本库。这里,要确认这个目录可以通过你选择的传输方式来被其他人访问。你也需要确认 你有git-receive-pack这个程序在$PATH这个路径下。
现在你的“公共的版本库”可以接受你的任何改动了。回到你的本地机上,运行命令: $ git push :/path/to/my-git.git master 该命令将你的公共版本库和你当前的版本库中指定名称的分支头部同步(这里是master)。举一个实际的例子,你可以 这样来更新公共的git版本库。Kernel.org的镜像网络也这样来同步其他公共的可访问的机器: $ git push master.kernel.org:/pub/scm/git/git.git/ 将工作捆绑到一起通过 git 的分支功能,你可以非常容易地做到好像在同一时间进行许多“相关-或-无关”的工作一样。 我们已经通过前面的 "fun and work" 使用两个分支的例子,看到分支是怎么工作的。 这样的思想在多于两个的分支的时候也是一样的,比方说,你现在在 master 的头, 并有些新的代码在 master 中,另外还有两个互不相关的补丁分别在 "commit-fix" 和 "diff-fix" 两个分支中。 $ git show-branch 两个补丁我们都测试好了,到这里,你想将他们俩合并起来, 于是你可以先合并 diff-fix ,然后再合并 commit-fix,像这样: $ git merge 'Merge fix in diff-fix' master diff-fix 结果如下: $ git show-branch 然而,当你确信你手头上的确是一堆互不相关的项目变化时,就没有任何理由将这堆东西一个个地合并( 假如他们的先后顺序很重要,那么他们就不应该被定以为无关的变化), 你可以一次性将那两个分支合并到当前的分支中,首先我们将我们刚刚做过的事情逆转一下, 我们需要通过将 master 分支重置到 master~2 位置的方法来将它逆转到合并那两个分支之前的状态。 $ git reset --hard master~2 你可以用 git-show-branch 来确认一下的确是回到了两次 git-merge 的状态了。 现在你可以用一行命令将那两个分支导入的方式来替代两次运行( 也就是所谓的 炮制章鱼 -- making an Octopus)git-merge : $ git pull . commit-fix diff-fix 注意那些不适合制作章鱼的场合,尽管你可以那样做。一只“章鱼”往往可以使项目的提交历史更具可读性, 前提是你在同一时间导入的两份以上的变更是互不关联的。 然而,如果你在合并任何分支的过程中出现合并冲突,并且需要手工解决的话, 那意味着这些分支当中有相互干涉的开发工作在进行,那么你就应该将这个两个冲突先合并, 并且记录下你是如何解决这个冲突,以及你首先处理他们的理由。(译者按:处理完冲突之后, 你就可以放心制作“章鱼”了) 否则的话将会造成项目的发展历史很难跟踪。 管理版本库版本库的管理员可以用下面的工具来建立和维护版本库。
update hook howto 一个很好的管理中心版本库的例子。 例子
项目开发的模式推介尽管 git 是一个正式项目发布系统,它却可以方便地将你的项目建立在松散的开发人员组织形式上。 Linux 内核的开发,就是按这样的模式进行的。在 Randy Dunlap 的著作中("Merge to Mainline" 第17页) 就有很好的介绍(http://tinyurl.com/a2jdg)。 需要强调的是正真的非常规的开发组织形式, git 这种组织形式,意味着对于工作流程的约束,没有任何强迫性的原则。 你不必从唯一一个远程版本库中导入(工作目录)。 项目领导人(project lead)的工作推介
项目的子系统负责人(subsystem maintainer)也有自己的公共库,工作流程大致如下:
“一般开发人员”无须自己的公共库,大致的工作方式是:
Last updated 27-Mar-2006 15:20:34 UTC
学习 Git作者:孔建军1 Git 介绍Git
版本控制工具的作者是Linux之父Linus
Trovalds,最初是专门针对Linux内核开发的特点编写的,即协作人员异地分布、人数众多、项目规模巨大、复杂度高等。与常用的版本控制
CVS,Subversion等不同,它采用了分布式版本库的方式,不毕服务器断软件支持,使源代码的发布和维护极其方便。Git的本地查询、搜索,补丁
制作、提交和应用,项目跟踪,分支合并等功能,可以大大提高开发效率,具有较强的灵活性。有人认为Git太艰涩难懂,实际上结合一些有用的脚本命令使用,
会使其变得非常好用。 2 Git 配置Git命令的使用,一般有两种两种形式,一种是git后面带参数(如:git add),另一种是直接减号连接的一条命令(如:git-add),后面讲解全部使用后者,这样可以避免空格的使用带来的问题。
3 Git 使用
4 Git的项目开发模式Git作为一个正式项目发布系统,它能够极其有效的组织松散的开发人员,是一种非常规的开发组织形式,对工作流程没有任何强迫性的约束,比较灵活。
5 免费git项目注册网址:http://repo.or.cz 0、使用git-init-db在本地创建版本库; 6 总结7 参考资料
February 26 Collection Class Notes------especial CTypedPtrList
Expand the Header Files folder, and open file StdAfx.h. At the end of the file, type: #include <afxtempl.h> // MFC templates ///////////////////////////////////////////////////////////////////////// CTypedPtrList < class BASE_CLASS, class TYPE > BASE_CLASS Base class of the typed pointer list class; must be a pointer list class (CObList or CPtrList). TYPE Type of the elements stored in the base-class list. The CTypedPtrList class provides a type-safe “wrapper” for objects of class CPtrList. When you use CTypedPtrList rather than CObList or CPtrList, the C++ type-checking facility helps eliminate errors caused by mismatched pointer types. In addition, the CTypedPtrList wrapper performs much of the casting that would be required if you used CObList or CPtrList. ================================================== Class Members ================================================== AddHead POSITION AddHead( TYPE newElement ); void AddHead( CTypedPtrList<BASE_CLASS, TYPE> *pNewList ); AddTail POSITION AddTail( TYPE newElement ); void AddTail( CTypedPtrList<BASE_CLASS, TYPE> *pNewList ); GetAt TYPE& GetAt( POSITION position ); TYPE GetAt( POSITION position ) const; GetHead TYPE& GetHead( ); TYPE GetHead( ) const; GetNext TYPE& GetNext( POSITION& rPosition ); TYPE GetNext( POSITION& rPosition ) const; GetPrev TYPE& GetPrev(POSITION& rPosition ); TYPE GetPrev( POSITION& rPosition ) const; GetTail TYPE& GetTail( ); TYPE GetTail( ) const; RemoveHead TYPE RemoveHead( ); RemoveTail TYPE RemoveTail( ); SetAt void SetAt( POSITION pos, TYPE newElement ); ================================================== Create ================================================== typedef CTypedPtrList<CPtrList, CMyStruct*> CMyStructList; CMyStructList m_myPtrList; typedef CTypedPtrList<CObList, CMyObject*> CMyObList; CMyObList m_myObList; ================================================== Insert ================================================== CMyStruct* pMyStruct = new CMyStruct(); pMyStruct->m_int = 1234; pMyStruct->m_float = 12.34f; pMyStruct->m_str.LoadString(IDS_INITIAL_STRING); m_myPtrList.AddTail(pMyStruct); m_myPtrList.InsertBefore(pos, pMyStruct); CMyObject* pMyObject = new CMyObject(); m_myObList.AddTail(pMyObject); ================================================== Iterate ================================================== POSITION pos = m_myPtrList.GetHeadPosition(); while( pos != NULL ) { CMyStruct* pMyStruct = m_myPtrList.GetNext( pos ); } ================================================== Find ================================================== pos = m_myPtrList.Find(pMyStruct); ================================================== Update ================================================== m_myPtrList.SetAt(pos, pMyStruct); ================================================== Delete ================================================== m_myPtrList.RemoveAt(pos); POSITION pos = m_myPtrList.GetHeadPosition(); while (pos != NULL) { delete m_myPtrList.GetNext(pos); } m_myPtrList.RemoveAll(); while (!m_myObList.IsEmpty()) { delete m_myObList.GetHead(); m_myObList.RemoveHead(); } ================================================== Serialize ================================================== nCount = (WORD)m_myPtrList.GetCount(); if (ar.IsStoring()) { ar << nCount; pos = m_myPtrList.GetHeadPosition(); while (pos != NULL) { CMyStruct* pMyStruct = m_myPtrList.GetNext(pos); w = (WORD)pMyStruct->m_int; ar << w; ar << pMyStruct->m_float; ar << pMyStruct->m_str; nCount--; } ASSERT(nCount == 0); } else { ar >> nCount; while (nCount-- > 0) { CMyStruct* pMyStruct = new CMyStruct; ar >> w; pMyStruct->m_int = w; ar >> pMyStruct->m_float; ar >> pMyStruct->m_str; m_myPtrList.AddTail(pMyStruct); } } m_myObList.Serialize(ar); // Note: CMyObject serializes itself void CMyObject::Serialize(CArchive& ar) { CObject::Serialize( ar ); if( ar.IsStoring() ) ar << i; else ar >> i; } January 13 Index.dat Analyzer v2.5Index.dat Analyzer is a tool to view, examine and delete contents of index.dat files.
Index.dat files are hidden files on your computer that contain all tracks of your online activity, where have you been on internet, what sites you visited, list of URL-s, files and documents you recently accessed. Index.dat files stored on your computer are obviously a potential privacy threat as they can be found and viewed without your knowledge. Windows Sysinternals Suite Build 2009.01.12Sysinternals Suite是微软发布的一套非常强大的免费工具程序集.我想介绍就不用多说了吧.用好Windows Sysinternals Suite里的工具,你将更有能力处理Windows的各种问题,而且不花一毛钱.Sysinternals 之前为Winternals公司提供的免费工具,Winternals原本是一间主力产品为系统复原与资料保护的公司,为了解决工程师平常在工作上遇到的各种问题,便开发出许多小工具.之后他们将这些工具集合起来称为Sysinternals,并放在网路供人免费下载,其中也包含部分工具的原始码,一直以来都颇受IT专家社群的好评. The Suite is a bundling of the following selected Sysinternals Utilities: September 02 InfoWorld2008最佳开源软件大奖新闻来源:CHIP中文版 事实上,InfoWorld的年度开源软件大奖很有分量,不过遗憾的是因为没有中文版本,所以很少有国内用户关注这个奖项。 InfoWorld 2008年的"开源软件大奖"最新出炉,CHIP软件社区乘此机会将InfoWorld 2008年的"开源软件大奖"中文化并进行整理,希望能够为中国用户带来便利,也希望能够为开源社区共享绵薄之力。 事实上,千万不要把开源软件想得那么神秘,比如在"InfoWorld2008最佳开源软件大奖"中:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Windows | Linux | 类别 |
CreateProcess() CreateProcessAsUser()
| fork() setuid() exec() | 可映射
|
TerminateProcess()
| kill()
| 可映射
|
SetThreadpriority() GetThreadPriority()
| Setpriority() getPriority()
| 可映射
|
GetCurrentProcessID()
| getpid()
| 可映射
|
Exitprocess()
| exit()
| 可映射
|
Waitforsingleobject() Waitformultipleobject() GetExitCodeProcess()
| waitpid() Using Sys V semaphores, Waitforsingleobject/multipleobject 不能实现
| 与上下文相关
|
GetEnvironmentVariable SetEnvironmentVariable
| getenv() setenv()
| 可映射 |
"类别"一列(解释了本文中所使用的分类结构)表明了 Windows 结构是否 可映射 或者 与上下文相关:
在 Windows 中,您可以使用 CreateProcess() 来创建一个新的进程。 CreateProcess() 函数创建一个新的进程及其主线程,如下:
BOOL CreateProcess( LPCTSTR lpApplicationName, // name of executable module LPTSTR lpCommandLine, // command line string LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD BOOL bInheritHandles, // handle inheritance option DWORD dwCreationFlags, // creation flags LPVOID lpEnvironment, // new environment block LPCTSTR lpCurrentDirectory, // current directory name LPSTARTUPINFO lpStartupInfo, // startup information LPPROCESS_INFORMATION lpProcessInformation // process information ) |
bInheritHandles 确定了子进程是否要继承父进程的句柄。lpApplicationName 和 lpCommandLine 给出了将要被启动的进程的名称与路径。lpEnvironment 定义了进程可使用的环境变量。
在 Linux 中,exec* 家族函数使用一个新的进程映像取代当前进程映像(如下所示):
int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg , ..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); |
exec* 的这些版本只是内核函数 execve() (int execve(const char *filename, char *const argv [], char *const envp[]))的各种调用接口。在这里,argv 是包含有参数 list 的指针,envp 是包含有环境变量列表(主要是 key=value 对)的指针。
它必须与 fork() 命令一起使用,所以父进程和子进程都在运行: pid_t fork(void)。fork() 会创建一个子进程,与父进程相比只是 PID 和 PPID 不同;实际上,资源利用设为 0。
默认情况下,exec() 继承父进程的组和用户 ID,这就使得它会依赖于父进程。可以使用以下方法来改变:
set-uid 和 set-gid 位
setpgid() 和 setuid() 系统调用 CreateProcessAsUser() 函数与 CreateProcess() 类似,只是新进程是在用户通过 hToken 参数描述的安全上下文中运行。在 Linux 中,没有与此函数惟一对应的函数,但是可以使用下面的逻辑来实现对它的复制:
fork() 创建一个具有新的 PID 的子进程
setuid() 切换到那个新的 PID
exec() 将现有进程改变为将要执行的进程 在 Windows 中,您可以使用 TerminateProcess() 强制终止一个运行中的进程。
BOOL TerminateProcess( HANDLE hProcess, // handle to the process UINT uExitCode // exit code for the process ); |
这个函数终止运行中的进程及其相关线程。只是在非常极端的场合才会使用这个函数。
在 Linux 中,您可以使用 kill() 来强行杀死一个进程: int kill(pid_t pid, int sig)。这个系统调用会终止 id 为 PID 的进程。您也可以使用它向任何组或者进程发出信号。
在子进程依赖于父进程的情况下,您可以在父进程中使用等待函数来等待子进程的终止。在 Windows 中,您可以使用 WaitForSingleObject() 函数调用来实现此功能。
您可以使用 WaitForMultipleObject() 函数来等待多个对象。
DWORD WaitForMultipleObjects( DWORD nCount, // number of handles in array CONST HANDLE *lpHandles, // object-handle array BOOL bWaitAll, // wait option DWORD dwMilliseconds // time-out interval ); |
您可以向对象句柄数组(object-handle array)中填充很多需要等待的对象。根据 bWaitALL 选项,您既可以等待所有对象被信号通知,也可以等待其中任意一个被信号通知。
在这两个函数中,如果您想等待有限的一段时间,则可以在第二个参数中指定时间间隔。如果您想无限制等待,那么使用 INFINITE 作为 dwMilliseconds 的值。将 dwMilliseconds 设置为 0 则只是检测对象的状态并返回。
在 Linux 中,如果您希望无限期等待进程被杀死,则可以使用 waitpid()。在 Linux 中,使用 waitpid() 调用无法等待限定的时间。
在这段代码中:pid_t waitpid(pid_t pid, int *status, int options),waitpid() 会无限期等待子进程的终止。在 Windows 和 Linux 中,等待函数会挂起当前进程的执行,直到它完成等待,不过,在 Windows 中可以选择在指定的时间后退出。使用 System V 信号量,您可以实现类似于 WaitForSingleObject() 和 WaitForMultipleObject() 的限时等待或者 NO WAIT 功能,在本系列的第 2 部分中将讨论此内容。本系列的第 3 部分将深入讨论等待函数。
退出进程指的是优雅(graceful)地退出进程,并完成适当的清除工作。在 Windows 中,您可以使用 ExitProcess() 来执行此操作。
VOID ExitProcess( UINT uExitCode // exit code for all threads ); |
ExitProcess() 是在进程结束处执行的方法。这个函数能够干净地停止进程。包括调用所有链接到的动态链接库(DLL)的入口点函数,给出一个值,指出这个进程正在解除那个 DLL 的链接。
Linux 中与 ExitProcess() 相对应的是 exit():void exit(int status);。
exit() 函数会令程序正常终止,并将 &0377 状态值返回给父进程。 C 语言标准规定了两个定义(EXIT_SUCCESS 和 EXIT_FAILURE),可以被传递到状态参数,以说明终止成功或者不成功。
每个进程都拥有关联到它的一组环境,其中主要是 name=value 对,指明进程可以访问的各种环境变量。尽管我们可以在创建进程时指定环境,不过也有特定函数可以在进程创建后设置和获得环境变量。
在 Windows 中,您可以使用 GetEnvironmentVariable() 和 SetEnvironmentVariable() 来获得和设置环境变量。
DWORD GetEnvironmentVariable( LPCTSTR lpName, // environment variable name LPTSTR lpBuffer, // buffer for variable value DWORD nSize // size of buffer ); |
如果成功,则此函数返回值缓存的大小,如果指定的名称并不是一个合法的环境变量名,则返回 0。 SetEnvironmentVariable() 函数为当前进程设置指定的环境变量的内容。
BOOL SetEnvironmentVariable( LPCTSTR lpName, // environment variable name LPCTSTR lpValue // new value for variable ); |
如果函数成功,则返回值非零。如果函数失败,则返回值为零。
在 Linux 中,getenv() 和 setenv() 系统调用提供了相应的功能。
char *getenv(const char *name); int setenv(const char *name, const char *value, int overwrite); |
getenv() 函数会在环境列表中搜索与名称字符串相匹配的字符串。这个函数会返回一个指向环境中的值的指针,或者如果不匹配则返回 NULL。setenv() 函数将变量名和值添加到环境中,如果那个名称并不存在。如果环境中已经存在那个名称,而且如果 overwrite 非零,则它的值会被修改为 value。如果 overwrite 为零,则 name 的值不会被改变。如果成功,则 setenv() 会返回零,如果环境中空间不足,则返回 -1。
下面的例子解释了我们在本节中讨论的内容。
//Sample Application that explain process concepts
//Parameters Declaration/Definition
int TimetoWait;
STARTUPINFO si;
PROCESS_INFORMATION pi;
LPTSTR lpszCurrValue,LPTSTR lpszVariable;
TCHAR tchBuf[BUFSIZE];
BOOL fSuccess;
if(argc > 2)
{
printf("InvalidArgument");
ExitProcess(1); //Failure
}
//Get and display an environment variable PATH
lpszCurrValue = ((GetEnvironmentVariable("PATH",tchBuf, BUFSIZE) > 0) ? tchBuf : NULL);
lpszVariable = lpszCurrValue;
//Display the environment variable
while (*lpszVariable)
putchar(*lpszVariable++);
putchar('\n');
//Initialise si and pi
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
//Create a childProcess
if( !CreateProcess( NULL, // No module name (use command line).
"SomeProcess", // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
FALSE, // Set handle inheritance to FALSE.
0, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi ) // Pointer to PROCESS_INFORMATION structure.
)
{
printf( "CreateProcess failed." );
}
// Wait until child process exits.
if(argc == 2)
{
TIMEOUT = atoi(argv[1]);
ret = WaitForSingleObject( pi.hProcess, TIMEOUT );
if(ret == WAIT_TIMEOUT)
{
TerminateProcess(pi.hProcess);
}
}
else
{
WaitForSingleObject( pi.hProcess, INFINITE );
...
}
ExitProcess(0); //Success
|
#include <stdlib.h>
int main(int argc,char *argv[])
{
//Parameters Declaration/Definition
char PathName[255];
char *Argptr[20];
int rc;
char *EnvValue,*lpszVariable;
if(argc > 1)
{
printf(" Wrong parameters !!");
exit(EXIT_FAILURE);
}
//Get and display an environment variable PATH
EnvValue = getenv("PATH");
if(EnvValue == NULL)
{
printf("Invalid environment variable passed as param !!");
}else
{
lpszVariable = EnvValue;
while (*lpszVariable)
putchar(*lpszVariable++);
putchar('\n');
}
rc = fork(); //variable rc's value on success would be process ID in the parent
//process, and 0 in the child's thread of execution.
switch(rc)
{
case -1:
printf("Fork() function failed !!");
ret = -1;
break;
case 0:
printf("Child process...");
setpgid(0,0); //Change the parent grp ID to 0
ret = execv(PathName,Argptr); // there are other flavours of exec available,
// u can use any of them based on the arguments.
if(ret == -1)
{
kill(getpid(),0);
}
break;
default:
// infinitely waits for child process to die
Waitpid(rc,&status,WNOHANG);
//Note RC will have PID returned since this is parent process.
break;
}
exit(EXIT_SUCCESS);
}
|
|
在 Windows 中,线程是基本的执行单位。在进程的上下文中会有一个或多个线程在运行。调度代码在内核中实现。没有单独的"调度器(scheduler)"模块或例程。
Linux 内核使用的是进程模型,而不是线程模型。Linux 内核提供了一个轻量级进程框架来创建线程;实际的线程在用户空间中实现。在 Linux 中有多种可用的线程库(LinuxThreads、NGPT、NPTL 等等)。本文中的资料基于 LinuxThreads 库,不过这里的资料也适用于 Red Hat 的 Native POSIX Threading Library(NPTL)。
本节描述 Windows 和 Linux 中的线程。内容涵盖了创建线程、设置其属性以及修改其优先级。
| Windows | Linux | 类别 |
CreateThread
| pthread_create pthread_attr_init pthread_attr_setstacksize pthread_attr_destroy
| 可映射
|
ThreadExit
| pthread_exit
| 可映射
|
WaitForSingleObject
| pthread_join pthread_attr_setdetachstate pthread_detach
| 可映射
|
SetPriorityClass SetThreadPriority
| setpriority sched_setscheduler sched_setparam pthread_setschedparam pthread_setschedpolicy pthread_attr_setschedparam pthread_attr_setschedpolicy
| 与上下文相关 |
在 Windows 中,您可以使用 CreateThread() 来创建线程,创建的线程在调用进程的虚拟地址空间中运行。
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD SIZE_T dwStackSize, // initial stack size LPTHREAD_START_ROUTINE lpStartAddress, // thread function LPVOID lpParameter, // thread argument DWORD dwCreationFlags, // creation option LPDWORD lpThreadId // thread identifier ); |
lpThreadAttributes 是指向线程属性的指针,决定了线程句柄是否能由子进程继承。
Linux 使用 pthread 库调用 pthread_create() 来派生线程:
int pthread_create (pthread_t *thread_id, pthread_attr_t *threadAttr,
void * (*start_address)(void *), void * arg);
|
注意:在 Windows 中,受可用虚拟内存的限制,一个进程可以创建的线程数目是有限的。默认情况下,每个线程有一兆栈空间。因此,您最多可以创建 2,028 个线程。如果您减小默认栈大小,那么可以创建更多线程。在 Linux 中,使用 ULIMIT -a(limits for all users)可以获得每个用户可以创建的线程的最大数目,可以使用 ULIMIT -u 来修改它,不过只有在登录时才可以这样做。 /usr/Include/limit.h 和 ulimit.h 下的头文件定义了这些内容。您可以修改它们并重新编译内核,以使其永久生效。对于 POSIX 线程限制而言,local_lim.h 中定义的 THREAD_THREADS_MAX 宏定义了数目的上限。
CreateThread() 中的 lpStartAddress 参数是刚创建的线程要执行的函数的地址。
pthread_create() 库调用的 start_address 参数是刚创建的线程要执行的函数的地址。
在 Windows 中,系统调用 CreateThread() 的参数 lpParameter 指定了要传递给刚创建的线程的参数。它指明了将要传递给新线程的数据条目的地址。
在 Linux 中,库调用 pthread_create() 的参数 arg 指定了将要传递给新线程的参数。
在 Windows 中,CreateThread() 的参数 dwStackSize 是将要分配给新线程的以字节为单位的栈大小。栈大小应该是 4 KB 的非零整数倍,最小为 8 KB。
在 Linux 中,栈大小在线程属性对象中设置;也就是说,将类型为 pthread_attr_t 的参数 threadAttr 传递给库调用 pthread_create()。在设置任何属性之前,需要通过调用 pthread_attr_init() 来初始化这个对象。使用调用 pthread_attr_destroy() 来销毁属性对象:
int pthread_attr_init(pthread_attr_t *threadAttr); int pthread_attr_destroy(pthread_attr_t *threadAttr); |
注意,所有 pthread_attr_setxxxx 调用都有与 pthread_xxxx 调用(如果有)类似的功能,只是您只能在线程创建之前使用 pthread_attr_xxxx,来更新将要作为参数传递给 pthread_create 的属性对象。同时,您在创建线程之后的任意时候都可以使用 pthread_xxxx。
使用调用 pthread_attr_setstacksize() 来设置栈大小: int pthread_attr_setstacksize(pthread_attr_t *threadAttr, int stack_size);。
在 Windows 中,系统调用 ExitThread() 会终止线程。 dwExitCode 是线程的返回值,另一个线程通过调用 GetExitCodeThread() 就可以得到它。
VOID ExitThread( DWORD dwExitCode // exit code for this thread ); |
Linux 中与此相对应的是库调用 pthread_exit()。 retval 是线程的返回值,可以在另一个线程中通过调用 pthread_join() 来获得它: int pthread_exit(void* retval);。
在 Windows 中,没有保持关于线程终止的显式线程状态。不过,WaitForSingleObject() 让线程能够显式地等待进程中某个指定的或者非指定的线程终止。
在 Linux 中,默认以可连接(joinable)的状态创建线程。在可连接状态中,另一个线程可以同步这个线程的终止,使用函数 pthread_join() 来重新获得其终止代码。可连接的线程只有在被连接后才释放线程资源。
Windows 使用 WaitForSingleObject() 来等待某个线程终止:
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds ); |
其中:
hHandle 是指向线程句柄的指针。
dwMilliseconds 是以毫秒为单位的超时值。如果这个值被设置为 INFINITE,则它会无限期地阻塞进行调用的线程/进程。 Linux 使用 pthread_join() 来完成同样的功能: int pthread_join(pthread_t *thread, void **thread_return);。
在分离的状态中,线程终止后线程资源会立即被释放。通过对线程属性对象调用 pthread_attr_setdetachstate() 可以设置分离状态: int pthread_attr_setdetachstate (pthread_attr_t *attr, int detachstate);。以可连接状态创建的线程,稍后可以被转为分离状态,方法是使用 pthread_detach() 调用:int pthread_detach (pthread_t id);。
在 Windows 中,线程的优先级由其进程的优先级等级以及进程优先级等级中的线程优先级层次决定。在 Linux 中,线程本身就是一个执行单位,有其自己的优先级。它与其进程的优先级没有依赖关系。
在 Windows 中,您可以使用 SetPriorityClass() 来设置特定进程的优先级等级:
BOOL SetPriorityClass( HANDLE hProcess, // handle to the process DWORD dwPriorityClass // Priority class ); |
dwPriorityClass 是进程的优先级等级,它可以设置为下列值中的任意一个:
IDLE_PRIORITY_CLASS
BELOW_NORMAL_PRIORITY_CLASS
NORMAL_PRIORITY_CLASS
ABOVE_NORMAL_PRIORITY_CLASS
HIGH_PRIORITY_CLASS
REALTIME_PRIORITY_CLASS 一旦设置了进程的优先级等级,就可以使用 SetThreadPriority() 在进程的优先级等级内部设置线程的优先级层次:
BOOL SetThreadPriority( HANDLE hThread, int nPriority ); |
nPriority 是线程的优先级值,它被设置为下列之一;
THREAD_PRIORITY_ABOVE_NORMAL 将优先级设置为比优先级等级高 1 级。
THREAD_PRIORITY_BELOW_NORMAL 将优先级设置为比优先级等级低 1 级。
THREAD_PRIORITY_HIGHEST 将优先级设置为比优先级等级高 2 级。
THREAD_PRIORITY_IDLE 为 IDLE_PRIORITY_CLASS、BELOW_NORMAL_PRIORITY_CLASS、NORMAL_PRIORITY_CLASS、ABOVE_NORMAL_PRIORITY_CLASS 或 HIGH_PRIORITY_CLASS 进程将基优先级设置 1,为 REALTIME_PRIORITY_CLASS 进程将基优先级设置为 16。
THREAD_PRIORITY_LOWEST 将优先级设置为比优先级等级低 2 级。
THREAD_PRIORITY_NORMAL 为优先级等级设置为普通优先级。
THREAD_PRIORITY_TIME_CRITICAL 为 IDLE_PRIORITY_CLASS、BELOW_NORMAL_PRIORITY_CLASS、NORMAL_PRIORITY_CLASS、ABOVE_NORMAL_PRIORITY_CLASS 或 HIGH_PRIORITY_CLASS 进程将基优先级设置 15,为 REALTIME_PRIORITY_CLASS 进程将基优先级设置为 31。
|
为了结束这一期文章,让我们来看下面类型的进程和线程的一些例子:
使用 Linux 系统调用 setpriority() 来设置或者修改普通进程和线程的优先级层次。参数的范围是 PRIO_PROCESS。将 id 设置为 0 来修改当前进程(或线程)的优先级。此外,delta 是优先级的值 —— 这一次是从 -20 到 20。另外,要注意在 Linux 中较低的 delta 值代表较高的优先级。所以,使用 +20 设置 IDLETIME 优先级,使用 0 设置 REGULAR 优先级。
在 Windows 中,常规线程的优先级的范围是从 1(较低的优先级)到 15(较高的优先级)。不过,在 Linux 中,普通非实时进程的优先级范围是从 -20(较高的)到 +20(较低的)。在使用之前必须对此进行映射: int setpriority(int scope, int id, int delta);。
您可以使用 Linux 系统调用 sched_setscheduler() 来修改正在运行的进程的调度优先级: int sched_setscheduler(pit_t pid, int policy, const struct sched_param *param);。
参数 policy 是调度策略。policy 的可能的值是 SCHED_OTHER (常规的非实时调度)、SCHED_RR(实时 round-robin 策略)和 SCHED_FIFO(实时 FIFO 策略)。
在此,param 是指向描述调度优先级结构体的指针。它的范围是 1 到 99,只用于实时策略。对于其他的(普通的非实时进程),它为零。
在 Linux 中,作为一个大家所熟知的调度策略,也可以通过使用系统调用 sched_setparam 来仅修改进程优先级: int sched_setparam(pit_t pid, const struct sched_param *param);。
LinuxThreads 库调用 pthread_setschedparam 是 sched_setscheduler 的线程版本,用于动态修改运行着的线程的调度优先级和策略: int pthread_setschedparam(pthread_t target_thread, int policy, const struct sched_param *param);。
参数 target_thread 告知线程要修改谁的优先级;param 指定了优先级。
LinuxThreads 库会调用 pthread_attr_setschedpolicy,并且您可以在线程被创建之前使用 pthread_attr_setschedparam 来设置线程属性对象的调度策略和优先级层次:
int pthread_attr_setschedpolicy(pthread attr_t *threadAttr, int policy); int pthread_attr_setschedparam(pthread attr_t *threadAttr, const struct sched_param *param); |
在 Windows 中,实时线程的优先级范围是从 16(较低的优先级)到 31(较高的优先级)。在 Linux 中,实时线程的优先级范围是从 99(较高的)到 1(较低的优先级)。在使用前必须对此进行映射。
下面的清单阐述了本节中的概念。
Main Thread
enum stackSize = 120 * 1024 ;
// create a thread normal and real time thread
DWORD normalTId, realTID;
HANDLE normalTHandle, realTHandle;
normalTHandle = CreateThread(
NULL, // default security attributes
stackSize, // 120K
NormalThread, // thread function
NULL, // argument to thread function
0, // use default creation flags
&normalTId); // returns the thread identifier
// Set the priority class as "High priority"
SetPriorityClass(pHandle, HIGH_PRIORITY_CLASS);
normalTHandle = CreateThread(
NULL, // default security attributes
stackSize, // 120K
NormalThread, // thread function
NULL, // argument to thread function
0, // use default creation flags
&normalTId); // returns the thread identifier
CloseHandle(threadHandle);
...
...
// Thread function
DWORD WINAPI NormalThread ( LPVOID lpParam )
{
HANDLE tHandle,pHandle;
pHandle = GetCurrentProcess();
tHandle = GetCurrentThread();
// Set the priority class as "High priority"
SetPriorityClass(pHandle, HIGH_PRIORITY_CLASS);
// increase the priority by 2 points above the priority class
SetThreadPriority(tHandle,THREAD_PRIORITY_HIGHEST);
// perform job at high priority
...
...
...
// Reset the priority class as "Normal"
SetPriorityClass(pHandle, NORMAL_PRIORITY_CLASS);
// set the priority back to normal
SetThreadPriority(tHandle,THREAD_PRIORITY_NORMAL);
// Exit thread
ExitThread(0);
}
// Thread function
DWORD WINAPI RealTimeThread ( LPVOID lpParam )
{
HANDLE tHandle, pHandle ;
pHandle = GetCurrentProcess();
tHandle = GetCurrentThread ();
// Set the priority class as "Real time"
SetPriorityClass(pHandle, REALTIME_PRIORITY_CLASS);
// increase the priority by 2 points above the priority class
SetThreadPriority(tHandle,THREAD_PRIORITY_HIGHEST);
// do time critical work
...
...
...
// Reset the priority class as "Normal"
SetPriorityClass(pHandle, NORMAL_PRIORITY_CLASS);
// Reset the priority back to normal
SetThreadPriority(tHandle,THREAD_PRIORITY_NORMAL);
ExitThread(0);
}
|
static void * RegularThread (void *);
static void * CriticalThread (void *);
// Main Thread
pthread_t thread1, thread2; // thread identifiers
pthread_attr_t threadAttr;
struct sched_param param; // scheduling priority
// initialize the thread attribute
pthread_attr_init(&threadAttr);
// Set the stack size of the thread
pthread_attr_setstacksize(&threadAttr, 120*1024);
// Set thread to detached state. No need for pthread_join
pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);
// Create the threads
pthread_create(&thread1, &threadAttr, RegularThread, NULL);
pthread_create(&thread2, &threadAttr, CriticalThread,NULL);
// Destroy the thread attributes
pthread_attr_destroy(&threadAttr);
...
...
// Regular non-realtime Thread function
static void * RegularThread (void *d) {
int priority = -18;
// Increase the priority
setpriority(PRIO_PROCESS, 0, priority);
// perform high priority job
...
...
// set the priority back to normal
setpriority(PRIO_PROCESS, 0, 0);
pthread_exit(NULL);
}
// Time Critical Realtime Thread function
static void * CriticalThread (void *d) {
// Increase the priority
struct sched_param param; // scheduling priority
int policy = SCHED_RR; // scheduling policy
// Get the current thread id
pthread_t thread_id = pthread_self();
// To set the scheduling priority of the thread
param.sched_priority = 90;
pthread_setschedparam(thread_id, policy, ¶m);
// Perform time critical task
...
...
// set the priority back to normal
param.sched_priority = 0;
policy = 0; // for normal threads
pthread_setschedparam(thread_id, policy, ¶m);
....
....
pthread_exit(NULL);
}
|
|
本系列文章的第 1 部分已经给出了一个指南,能够帮助您将 Windows 进程和线程映射到 Linux 中的对应物。系列的第 2 部分介绍了同步对象和原语,首先是信号量和事件。第 3 部分介绍了互斥体、关键区域和等待函数。
|
|
Srinivasan S. Muthuswamy 是 IBM Global Services Group 的一位软件工程师。他于 2000 年加入 IBM,他所精通的编程语言涵盖了多种平台上(Linux、Windows、WebSphere、Lotus 等等)的脚本语言以及面向对象和面向过程的语言。他已经开发的解决方案包括 Linux 和 Windows 上的系统编程以及用于 J2EE 的 Web 解决方案。他在印度的 Coimbatore 国立科技大学(Government College of Technology)获得计算机工程学士学位,主要致力于集成和迁移。您可以通过 smuthusw@in.ibm.com 与他联系。 | |
|
|
从 2000 年 12 月起,Kavitha Varadarajan 一直在 IBM India Software Lab 担任软件工程师。她的工作经验包括 host-access 客户机产品(比如 PCOMM)和网络软件(比如通信服务器)的开发与支持。Varadarajan 拥有迁移项目的实践经验,其中涉及到了面向对象 IPC Windows 应用程序向 Linux 的移植。她拥有印度的 Tanjore 山姆哈工程大学(Shanmugha College of Engineering)的计算机科学与工程硕士学位。您可以通过 vkavitha@in.ibm.com 与她联系。 | |
|
|